标题: 一个有限制的排序算法题 [打印本页]
作者: 老刘1号 时间: 2019-1-22 12:17 标题: 一个有限制的排序算法题
昨天群里看到的,
【吐槽】Tiger<haotian.fan@qq.com> 下午 2:09:55
网上看见一道题目,问下大家,
如果一个List只能移动第一项,请问如何排序?
【中华】zhonghua(1138520994) 下午 3:02:33
@Tiger Tiger 只能移动第一项是啥意思,栈之类的结构么
【吐槽】Tiger<haotian.fan@qq.com> 下午 3:02:56
就比如说 5 1 3 2 4只能移动5,不能移动其他元素
【吐槽】Tiger<haotian.fan@qq.com> 下午 3:03:11
stack overflow看到的,有点解不开
【中华】zhonghua(1138520994) 下午 3:04:28
这个移动是啥意思
pop(0)?
【吐槽】Tiger<haotian.fan@qq.com> 下午 3:04:54
比如说移到3后面就编程1 3 5 2 4
我的解法(伪插入排序,效率底下,各位大佬见笑):- @echo off
- Setlocal enabledelayedexpansion
- ::CODER BY 老刘 POWERD BY iBAT
- Set /p List=
- Call :GetList !List!
- Set /A Count=1
- :Loop 首项逆向比较
- Set /A Flag1=0
- For /l %%a in (!prt! -1 1) Do (
- If !.1! Gtr !.%%a! (
- Call :MoveTo %%a
- Set /A Flag1=1
- Goto :JmpOut
- )
- )
- :JmpOut
- If !Flag1! Equ 1 Goto Loop
- Set /A Count+=1
- If !Count! Leq !prt! (
- Call :MoveTo !Count!
- Goto :Loop
- )
- For /l %%a in (1 1 !prt!) Do Set .%%a
- pause&"%~0"
-
- :GetList
- Set /A Prt=0
- Set list=%*
- For %%a In (%*) Do (
- Set /A Prt+=1
- Set .!Prt!=%%a
- )
- Goto :Eof
-
- :MoveTo 移动到原先第%1项的后面
- Set /A #=.1
- For /l %%a in (1 1 %1) Do (
- Set /A PlusOne=%%a+1
- Set /A .%%a=.!PlusOne!
- )
- Set /A .%1=#
- Goto :Eof
复制代码
作者: 523066680 时间: 2019-1-22 12:34
本帖最后由 523066680 于 2019-1-22 12:44 编辑
回复 1# 老刘1号
问题1,只有首个元素可以移动,他可以移动到任意位置吗
若按C数组操作,如果你可以移动到中间位置,那不是还等于其他元素也可以移动了
问题2,想看stackoverflow原帖,也许是leetcode上面的题目?
作者: 老刘1号 时间: 2019-1-22 12:52
本帖最后由 老刘1号 于 2019-1-24 13:06 编辑
回复 2# 523066680
刚去拷问了一波问主,原话
每次移动完以后,只能移动第一个,插入以后,列表开头就是新的可移动元素
作者: 523066680 时间: 2019-1-22 12:57
本帖最后由 523066680 于 2019-1-22 13:13 编辑
int a[5] = {a,b,c,d,e} 把数组变成 {b,c,a,d,e}
b和c 不是从 1,2的位置变成了 0,1的位置吗。(这种允许的话,感觉跟打牌手法的插入排序差不多。)
除非说这是一个定制的数据结构,每个数之间有足够的间隙,开头的数字可以插入任意数字中间而其他数字不需要移动。
比如 a,,,,b,,,,c,,,,d,,,,e,,,,
回复 5# 老刘1号
不纠结数据结构,纠结的是描述。觉得你现在的描述就比较合理了,其他数字被动移动。
作者: 老刘1号 时间: 2019-1-22 13:10
回复 4# 523066680
总觉得不需要纠结结构……
我觉得问主的意思就是,第一个随意移动,其余的被动移动,顺便改变位置
作者: 老刘1号 时间: 2019-1-22 13:29
补一个优化版- @echo off
- Setlocal enabledelayedexpansion
- ::CODER BY 老刘 POWERD BY iBAT
- Set /p List=
- Call :GetList !List!
- Set /A Count=1
- :Loop
- Rem 首项逆向比较
- Set /A Flag1=0
- For /l %%a in (!prt! -1 1) Do (
- If !.1! Gtr !.%%a! (
- Call :MoveTo %%a
- set/p=移动到%%a后:<nul
- For /l %%a in (1 1 !prt!) Do Set/p=!.%%a!-<nul
- Echo.
- Set /A Flag1=1
- Goto :JmpOut
- )
- )
- :JmpOut
- If !Flag1! Equ 1 Goto Loop
-
- Rem 判断是否排序完成。
- Set /A Flag2=0
- For /l %%a in (1 1 !prt!) Do (
- Set /A PlusOne=%%a+1
- Set /A "#=.!PlusOne!"
- If !#! Lss !.%%a! (
- Set Flag2=1
- If %%a equ !prt! Set Flag2=0
- Goto :JmpOut2
- )
- )
- :JmpOut2
- If !Flag2! Equ 0 Goto End
-
- Rem 继续排序
- Set /A Count+=1
- If !Count! Leq !prt! (
- Call :MoveTo !Count!
- Set/p=移动到!Count!后:<nul
- For /l %%a in (1 1 !prt!) Do Set/p=!.%%a!-<nul
- Echo.
- Goto :Loop
- )
- :End
- For /l %%a in (1 1 !prt!) Do Set/p=!.%%a!-<nul
- Echo.&pause&"%~0"
-
- :GetList
- Set /A Prt=0
- Set list=%*
- For %%a In (%*) Do (
- Set /A Prt+=1
- Set .!Prt!=%%a
- )
- Goto :Eof
-
- :MoveTo 移动到原先第%1项的后面
- Set /A #=.1
- For /l %%a in (1 1 %1) Do (
- Set /A PlusOne=%%a+1
- Set /A .%%a=.!PlusOne!
- )
- Set /A .%1=#
- Goto :Eof
复制代码
- 5 1 3 2 4
- 移动到5后:1-3-2-4-5-
- 移动到2后:3-1-2-4-5-
- 移动到3后:1-2-3-4-5-
- 1-2-3-4-5-
- 请按任意键继续. . .
- 3 2 1
- 移动到3后:2-1-3-
- 移动到2后:1-2-3-
- 1-2-3-
- 请按任意键继续. . .
- 7 3 4 1 10 9 8 11 24
- 移动到4后:3-4-1-7-10-9-8-11-24-
- 移动到3后:4-1-3-7-10-9-8-11-24-
- 移动到3后:1-3-4-7-10-9-8-11-24-
- 移动到2后:3-1-4-7-10-9-8-11-24-
- 移动到2后:1-3-4-7-10-9-8-11-24-
- 移动到3后:3-4-1-7-10-9-8-11-24-
- 移动到3后:4-1-3-7-10-9-8-11-24-
- 移动到3后:1-3-4-7-10-9-8-11-24-
- 移动到4后:3-4-7-1-10-9-8-11-24-
- 移动到4后:4-7-1-3-10-9-8-11-24-
- 移动到4后:7-1-3-4-10-9-8-11-24-
- 移动到4后:1-3-4-7-10-9-8-11-24-
- 移动到5后:3-4-7-10-1-9-8-11-24-
- 移动到5后:4-7-10-1-3-9-8-11-24-
- 移动到5后:7-10-1-3-4-9-8-11-24-
- 移动到5后:10-1-3-4-7-9-8-11-24-
- 移动到7后:1-3-4-7-9-8-10-11-24-
- 移动到6后:3-4-7-9-8-1-10-11-24-
- 移动到6后:4-7-9-8-1-3-10-11-24-
- 移动到6后:7-9-8-1-3-4-10-11-24-
- 移动到6后:9-8-1-3-4-7-10-11-24-
- 移动到6后:8-1-3-4-7-9-10-11-24-
- 移动到5后:1-3-4-7-8-9-10-11-24-
- 1-3-4-7-8-9-10-11-24-
- 请按任意键继续. . .
- 1 2 3 5 4
- 移动到2后:2-1-3-5-4-
- 移动到2后:1-2-3-5-4-
- 移动到3后:2-3-1-5-4-
- 移动到3后:3-1-2-5-4-
- 移动到3后:1-2-3-5-4-
- 移动到4后:2-3-5-1-4-
- 移动到4后:3-5-1-2-4-
- 移动到4后:5-1-2-3-4-
- 移动到5后:1-2-3-4-5-
- 1-2-3-4-5-
- 请按任意键继续. . .
复制代码
作者: 523066680 时间: 2019-1-22 13:49
本帖最后由 523066680 于 2019-1-22 15:13 编辑
有缺陷/未完成,大概方向,unshift 取出首个数字,和剩下的各个元素做减法,找出相差距离最近的数字(分为L和R)的对应坐标,插入。可能做足判断会好一些,待完善。- my @arr = qw/5 1 9 3 2 4 6/;
- my ($first, $L,$R,$r_id, $l_id, $dt);
- my $max = 100000;
-
- for (1..5)
- {
- $first = shift @arr;
- $L = -$max;
- $R = $max;
- $r_id = 0;
- $l_id = 0;
- for $i ( 0 .. $#arr ) {
- $dt = $arr[$i] - $first;
- if ( $dt > 0 && $dt <= $R) { $r_id = $i, $R = $dt; }
- if ( $dt < 0 && $dt >= $L) { $l_id = $i, $L = $dt; }
- }
-
- if ( $R == $max ) {
- push @arr, $first;
- } else {
- splice( @arr, $r_id, 0, $first );
- }
- #printf "%d - %d: %d, %d: %d\n", $first, $l_id, $L, $r_id, $R;
- print join(",", @arr), "\n";
- }
-
- #print join("", @arr);
复制代码
1,9,3,2,4,5,6
9,3,1,2,4,5,6
3,1,2,4,5,6,9
1,2,3,4,5,6,9
特地找一副扑克牌玩了一下,为啥人玩起来就这么自然…… 转成代码总是有纰漏,
纠正了某些情况下不能完全处理的问题,也未经过严格验证,暂时这样。- my @arr = qw/7 3 4 1 10 9 8 11 24/;
- my ($first, $L,$R,$r_id, $l_id, $dt);
- my $max = 100000;
- my $ok = 0;
-
- while (1)
- {
- $first = shift @arr;
- $L = -$max;
- $R = $max;
- $r_id = 0;
- $l_id = 0;
- for $i ( 0 .. $#arr ) {
- $dt = $arr[$i] - $first;
- if ( $dt > 0 && $dt <= $R) { $r_id = $i, $R = $dt; }
- if ( $dt < 0 && $dt >= $L) { $l_id = $i, $L = $dt; }
- }
-
- if ( $L != -$max )
- {
- # 如果有相对较小的数字,优先移至最邻近者
- splice( @arr, $l_id+1, 0, $first );
- } else {
- if ( $R == $max ) {
- # 如果未找到更大的数字,直接堆到最后
- push @arr, $first;
- } else {
- # 判断数组中是否还有 左边大于右边的情况
- $ok = 1;
- for my $i ( 0 .. $#arr-1 ) {
- if ( $arr[$i] > $arr[$i+1] ) {
- splice( @arr, $i+1, 0, $first );
- $ok = 0;
- last;
- }
- }
- last if $ok;
- }
- }
- #printf "%d -- [%d]: %d, [%d]: %d\n", $first, $l_id, $L, $r_id, $R;
- print join(",", @arr), "\n";
- }
复制代码
- 3,4,7,1,10,9,8,11,24
- 4,7,1,3,10,9,8,11,24
- 7,1,3,4,10,9,8,11,24
- 1,3,4,7,10,9,8,11,24
- 3,4,7,10,1,9,8,11,24
- 4,7,10,1,3,9,8,11,24
- 7,10,1,3,4,9,8,11,24
- 10,1,3,4,7,9,8,11,24
- 1,3,4,7,9,10,8,11,24
- 3,4,7,9,10,1,8,11,24
- 4,7,9,10,1,3,8,11,24
- 7,9,10,1,3,4,8,11,24
- 9,10,1,3,4,7,8,11,24
- 10,1,3,4,7,8,9,11,24
- 1,3,4,7,8,9,10,11,24
复制代码
- 2,3,5,1,4
- 3,5,1,2,4
- 5,1,2,3,4
- 1,2,3,4,5
复制代码
作者: 老刘1号 时间: 2019-1-22 13:57
本帖最后由 老刘1号 于 2019-1-22 14:28 编辑
回复 7# 523066680
好思路,若完成效率应该比我那个高
250技术不忍破坏
作者: 523066680 时间: 2019-1-22 15:37
回复 8# 老刘1号
已更新
作者: 老刘1号 时间: 2019-1-22 16:54
本帖最后由 老刘1号 于 2019-1-22 16:55 编辑
回复 9# 523066680
若不考虑效率的话,感觉可以把只移动首个元素这个限制用一个算法去除掉,直接完成任意两元素交换。
a b c d e f
b <-- --> e
b c d e f a
c d e b f a
d e b f a c
e b f a c d
b f a e c d
f a e c d b
a e c d b f
a b c d e f
a <-- --> c
b c a d e f
c a d e f b
a d e f c b
d e f c b a
e f c b a d
f c b a d e
c b a d e f
a b c
a <-- --> c
b c a
c a b
a c b
c b a
脑补算法中
作者: 523066680 时间: 2019-1-22 17:09
本帖最后由 523066680 于 2019-1-22 17:32 编辑
试试粗暴一点,递归求不同解、最优解。(逻辑不够完整,怕卡太久,限制了层数 lv <= 5),- my @arr = qw/8 9 1 2 5/;
- my @order = ();
- our @ways;
- solver(0, \@order, @arr);
-
- my $first;
- for my $w ( @ways )
- {
- printf "Solution: %s\n", join(",", @$w);
- my @brr = @arr;
- for my $mov ( @$w )
- {
- $first = shift @brr;
- splice( @brr, $mov, 0, $first );
- printf "%s\n", join(",", @brr);
- }
- printf "\n";
- }
-
- sub solver
- {
- my ($lv, $order) = (shift, shift);
- my @arr = @_;
- my @brr;
- my $first;
- my $res;
- return if $lv == 5;
- if (check(\@arr) == -1) {
- #printf "[%d] %s %s\n", $lv, join(",", @arr), join(",", @brr);
- for my $i ( 1 .. $#arr ) {
- @brr = @arr[1..$#arr];
- splice(@brr, $i, 0, $arr[0] );
- $order[$lv] = $i;
- $res = solver( $lv+1, $order, @brr );
- #if ( $res == 1 ) {last;}
- }
- return 1 if $res == 1;
- } else {
- #printf "%s\n", join(",", @arr);
- push @ways, [@{$order}[0..$lv-1] ];
- return 1;
- }
- }
-
- sub check
- {
- my $ar = shift;
- grep { return -1 if ( $ar->[$_] > $ar->[$_+1] ) } ( 0 .. $#$ar-1);
- return 1;
- }
复制代码
- Solution: 1,1,4,4
- 9,8,1,2,5
- 8,9,1,2,5
- 9,1,2,5,8
- 1,2,5,8,9
-
- Solution: 1,4,3
- 9,8,1,2,5
- 8,1,2,5,9
- 1,2,5,8,9
-
- Solution: 2,4,1,3
- 9,1,8,2,5
- 1,8,2,5,9
- 8,1,2,5,9
- 1,2,5,8,9
-
- Solution: 4,1,1,4
- 9,1,2,5,8
- 1,9,2,5,8
- 9,1,2,5,8
- 1,2,5,8,9
-
- Solution: 4,4
- 9,1,2,5,8
- 1,2,5,8,9
-
- [Finished in 0.1s]
复制代码
粗暴中发现那个数组的处理过程可以更短,6次解决:- Move Steps: 5,5,5,2,6,5
- 3,4,7,1,10,9,8,11,24 <- Original
- 4,7,1,10,9,3,8,11,24
- 7,1,10,9,3,4,8,11,24
- 1,10,9,3,4,7,8,11,24
- 10,9,1,3,4,7,8,11,24
- 9,1,3,4,7,8,10,11,24
- 1,3,4,7,8,9,10,11,24
复制代码
作者: 523066680 时间: 2019-1-22 17:43
本帖最后由 523066680 于 2019-1-22 17:51 编辑
回复 1# 老刘1号
Tiger就是那个初中一年级,做MIT英文原版数学试卷、精通C++、Python、JAVA,熟练 PyTorch、 Tensorflow、会写CNN DNN RNN KNN,的神奇少年吗。
看到那个群里这么多大神,吓得我退群点了个外卖。
作者: 老刘1号 时间: 2019-1-22 17:51
回复 12# 523066680
哈哈哈,人是那个人,群不是那个群
作者: 老刘1号 时间: 2019-1-22 18:44
本帖最后由 老刘1号 于 2019-1-22 19:11 编辑
回复 12# 523066680
伪元素交换的算法摸清了……
找个位置真费脑……杀了一大片脑细胞- @echo off
- Setlocal enabledelayedexpansion
- ::CODER BY 老刘 POWERD BY iBAT
- ::Set /p List=
- Call :GetList 1 2 3 4 5
- Call :Xchg 1 4
- Call :Echo
- Call :Xchg 2 3
- Call :Echo
- Call :Xchg 2 5
- Call :Echo
- Call :Xchg 3 4
- Call :Echo
- Pause
- Exit
-
- :GetList
- Set /A Prt=0
- Set list=%*
- For %%a In (%*) Do (
- Set /A Prt+=1
- Set .!Prt!=%%a
- )
- Goto :Eof
-
- :XChg 两元素互换
- Rem 将需要互换的第一个元素换到第一位
- Set /A SubOne=%1-1
- For /l %%a in (1 1 !SubOne!) Do Call :MoveTo !Prt!
- Set /a Steps+=SubOne
- Rem 将需要互换的第一个元素塞到第二个元素后面
- Rem 计算新list中第二个元素的位置
- Set /A Arg2Index=%2-SubOne
- Rem 挪
- Call :MoveTo !Arg2Index!
- Set /a Steps+=1
- Rem 将需要互换的第二个元素换到第一位
- Set /a Arg2SubArg1SubOne=%2-%1-1
- For /l %%a in (1 1 !Arg2SubArg1SubOne!) Do Call :MoveTo !Prt!
- Set /a Steps+=Arg2SubArg1SubOne
- Rem 将第二个元素塞到原先第一个元素的位置
- Rem 计算新list中第一个元素之前元素的位置
- Set /a 第一个元素之前元素的位置=(%1+1)-(SubOne+1+Arg2SubArg1SubOne)+prt-1
- Rem 挪
- Call :MoveTo !第一个元素之前元素的位置!
- Set /a Steps+=1
- Rem 还原其余各元素位置
- Set /A Reserve=prt-((SubOne+1+Arg2SubArg1SubOne)-1)-1
- For /l %%a in (1 1 !Reserve!) Do Call :MoveTo !Prt!
- Set /a Steps+=Reserve
- Goto :Eof
-
- :MoveTo 移动到原先第%1个元素的后面
- Set /A #=.1
- For /l %%a in (1 1 %1) Do (
- Set /A PlusOne=%%a+1
- Set /A .%%a=.!PlusOne!
- )
- Set /A .%1=#
- Goto :Eof
-
- :Echo
- For /l %%a in (1 1 !prt!) Do Set/p=!.%%a!-<nul
- Echo.
- Goto :Eof
复制代码
4-2-3-1-5-
4-3-2-1-5-
4-5-2-1-3-
4-5-1-2-3-
请按任意键继续. . .
作者: 523066680 时间: 2019-1-22 18:51
本帖最后由 523066680 于 2019-1-22 19:01 编辑
回复 14# 老刘1号
你用批处理,在解决问题的时候还要为批处理伤脑筋,很不容易~
要是换成其他脚本,各种骚操作、语法糖用了再说,舒服。
但仔细想想确实很蛋疼的感觉,不想写
作者: 老刘1号 时间: 2019-1-22 18:56
回复 15# 523066680
哈哈,这和批处理倒是关系不大, 主要是用“只移动第一个元素”来模拟“交换任意两个元素”比较蛋疼
作者: 523066680 时间: 2019-1-22 19:04
本帖最后由 523066680 于 2019-1-22 21:36 编辑
回复 16# 老刘1号
移动两个元素的同时还要保持其他元素的原有顺序,感觉好蛋疼
犯了一个数组坐标偏移计算错误,花了很多时间去矫正(就是临时数组消减后下标减小了,失算)- my @idx = qw/0 1 2 3 4/;
- my ($a, $b) = (3,4);
- my $apos = $a;
- my $bpos = $b;
- my $exchange = 0;
- printf "%d %d, %s\n", $apos, $bpos, join(",", @idx);
-
- # 首位数插入idx[$b]的前方
- $first = shift @idx;
- splice( @idx, $b-1, 0, $first );
- $apos-=1;
- printf "%d %d, %s\n", $apos, $bpos, join(",", @idx);
-
- while (1)
- {
- $first = shift @idx;
- if ( $first == $a ) {
- splice( @idx, $bpos, 0, $first );
- $apos = $bpos;
- $bpos -= 1;
- $exchange = 1;
- } else {
- if ( $bpos ne $a ) {
- if ($exchange) {
- splice( @idx, $apos-1, 0, $first );
- $bpos -= 1;
- } else {
- splice( @idx, $bpos-1, 0, $first );
- $apos -= 1;
- }
- } else {
- last;
- }
- }
- printf "%d %d, %s\n", $apos, $bpos, join(",", @idx);
- }
复制代码
前面两个数是位置变化- 3 4, 0,1,2,3,4
- 2 4, 1,2,3,0,4
- 1 4, 2,3,0,1,4
- 0 4, 3,0,1,2,4
- 4 3, 0,1,2,4,3
复制代码
作者: 老刘1号 时间: 2019-1-22 19:05
本帖最后由 老刘1号 于 2019-1-22 19:23 编辑
补一个冒泡排序- @echo off
- Setlocal enabledelayedexpansion
- ::CODER BY 老刘 POWERD BY iBAT
- Set /p List=
- Set /A Steps=0
- Call :冒泡排序算法 !List!
- Call :Echo
- Echo 第一个元素被移动的次数:!Steps!
- Pause
-
- :冒泡排序算法
- Call :GetList %*
- :Loop
- Set Flag=0
- For /l %%a in (2 1 !prt!) Do (
- Set /A SubOne=%%a-1
- Set /A #=.!SubOne!
- If !#! Gtr !.%%a! (
- Call :XChg !SubOne! %%a
- Set /A Flag=1
- )
- )
- If !Flag! Equ 1 Goto :Loop
- Goto :Eof
-
- :GetList
- Set /A Prt=0
- Set list=%*
- For %%a In (%*) Do (
- Set /A Prt+=1
- Set .!Prt!=%%a
- )
- Goto :Eof
-
- :XChg 两元素互换
- Rem 将需要互换的第一个元素换到第一位
- Set /A SubOne=%1-1
- For /l %%a in (1 1 !SubOne!) Do Call :MoveTo !Prt!
- Set /a Steps+=SubOne
- Rem 将需要互换的第一个元素塞到第二个元素后面
- Rem 计算新list中第二个元素的位置
- Set /A Arg2Index=%2-SubOne
- Rem 挪
- Call :MoveTo !Arg2Index!
- Set /a Steps+=1
- Rem 将需要互换的第二个元素换到第一位
- Set /a Arg2SubArg1SubOne=%2-%1-1
- For /l %%a in (1 1 !Arg2SubArg1SubOne!) Do Call :MoveTo !Prt!
- Set /a Steps+=Arg2SubArg1SubOne
- Rem 将第二个元素塞到原先第一个元素的位置
- Rem 计算新list中第一个元素之前元素的位置
- Set /a 第一个元素之前元素的位置=(%1+1)-(SubOne+1+Arg2SubArg1SubOne)+prt-1
- Rem 挪
- Call :MoveTo !第一个元素之前元素的位置!
- Set /a Steps+=1
- Rem 还原其余各元素位置
- Set /A Reserve=prt-((SubOne+1+Arg2SubArg1SubOne)-1)-1
- For /l %%a in (1 1 !Reserve!) Do Call :MoveTo !Prt!
- Set /a Steps+=Reserve
- Goto :Eof
-
- :MoveTo 移动到原先第%1个元素的后面
- Set /A #=.1
- For /l %%a in (1 1 %1) Do (
- Set /A PlusOne=%%a+1
- Set /A .%%a=.!PlusOne!
- )
- Set /A .%1=#
- Goto :Eof
-
- :Echo
- For /l %%a in (1 1 !prt!) Do Set/p=!.%%a!-<nul
- Echo.
- Goto :Eof
复制代码
7 3 4 1 10 9 8 11 24
1-3-4-7-8-9-10-11-24-
第一个元素被移动的次数:80
请按任意键继续. . .
作者: 老刘1号 时间: 2019-1-22 19:32
本帖最后由 老刘1号 于 2019-1-22 23:51 编辑
选择排序- @echo off
- Setlocal enabledelayedexpansion
- ::CODER BY 老刘 POWERD BY iBAT
- Set /p List=
- Set /A Steps=0
- Call :选择排序算法 !List!
- Call :Echo
- Echo 第一个元素被移动的次数:!Steps!
- Pause
-
- :选择排序算法
- Call :GetList %*
- Set /A Count=0
- :Loop
- Set /A Count+=1
- Set /A #=.!Count!
- For /l %%a in (!Count! 1 !prt!) Do (
- If !.%%a! Leq !#! (
- Set /A #=.%%a
- Set /A Index=%%a
- )
- )
- If !Count! Neq !Index! Call :XChg !Count! !Index!
- If !Count! Lss !prt! Goto :Loop
- Goto :Eof
-
- :GetList
- Set /A Prt=0
- Set list=%*
- For %%a In (%*) Do (
- Set /A Prt+=1
- Set .!Prt!=%%a
- )
- Goto :Eof
-
- :XChg 两元素互换
- Rem 将需要互换的第一个元素换到第一位
- Set /A SubOne=%1-1
- For /l %%a in (1 1 !SubOne!) Do Call :MoveTo !Prt!
- Set /a Steps+=SubOne
- Rem 将需要互换的第一个元素塞到第二个元素后面
- Rem 计算新list中第二个元素的位置
- Set /A Arg2Index=%2-SubOne
- Rem 挪
- Call :MoveTo !Arg2Index!
- Set /a Steps+=1
- Rem 将需要互换的第二个元素换到第一位
- Set /a Arg2SubArg1SubOne=%2-%1-1
- For /l %%a in (1 1 !Arg2SubArg1SubOne!) Do Call :MoveTo !Prt!
- Set /a Steps+=Arg2SubArg1SubOne
- Rem 将第二个元素塞到原先第一个元素的位置
- Rem 计算新list中第一个元素之前元素的位置
- Set /a 第一个元素之前元素的位置=(%1+1)-(SubOne+1+Arg2SubArg1SubOne)+prt-1
- Rem 挪
- Call :MoveTo !第一个元素之前元素的位置!
- Set /a Steps+=1
- Rem 还原其余各元素位置
- Set /A Reserve=prt-((SubOne+1+Arg2SubArg1SubOne)-1)-1
- For /l %%a in (1 1 !Reserve!) Do Call :MoveTo !Prt!
- Set /a Steps+=Reserve
- Goto :Eof
-
- :MoveTo 移动到原先第%1个元素的后面
- Set /A #=.1
- For /l %%a in (1 1 %1) Do (
- Set /A PlusOne=%%a+1
- Set /A .%%a=.!PlusOne!
- )
- Set /A .%1=#
- Goto :Eof
-
- :Echo
- For /l %%a in (1 1 !prt!) Do Set/p=!.%%a!-<nul
- Echo.
- Goto :Eof
复制代码
7 3 4 1 10 9 8 11 24
1-3-4-7-8-9-10-11-24-
第一个元素被移动的次数:20
请按任意键继续. . .
作者: search_Sudoku 时间: 2019-1-22 21:47
1 3 5 2 4
3 5 2 1 4
5 2 1 3 4
2 1 3 4 5
1 2 3 4 5
第 1 次 对 首元素 在 表后部 长度为 1 的子表中 检索(二分法) 找到 应插入的位置
第 2 次 对 首元素 在 表后部 长度为 2 的子表中 检索(二分法) 找到 应插入的位置
第 3 次 对 首元素 在 表后部 长度为 3 的子表中 检索(二分法) 找到 应插入的位置
第 4 次 对 首元素 在 表后部 长度为 4 的子表中 检索(二分法) 找到 应插入的位置
第 5 次 对 首元素 在 表后部 长度为 5 的子表中 检索(二分法) 找到 应插入的位置
... 对
第 i 次 对 首元素 在 表后部 长度为 i 的子表中 检索(二分法) 找到 应插入的位置
要排序的表元素为 n 个, 移动插入的总次数在最差情况下为 n
优化算法在于最优化检索算法, 比如用二分检索
作者: 523066680 时间: 2019-1-22 21:54
本帖最后由 523066680 于 2019-1-22 22:25 编辑
回复 20# search_Sudoku
惊醒梦中人,我竟然没意识到到这个过程同样可以构建一个逐步扩大的有序列表, why didn't I think of that!
这样完全可以获得[步数]在[元素个数]以内的解,无论效率还是实现,应该都是最佳方案。
本来我在想那个递归跑出来的最短步骤解,但是想想就像通常情况下的的排序,某些解同样有更快的步骤,这些步骤的缩减有时只能是针对性的,即使实现了,也会改变效率的“天枰”。
作者: search_Sudoku 时间: 2019-1-22 22:12
本帖最后由 search_Sudoku 于 2019-1-23 10:09 编辑
回复 21# 523066680
子表 的起始长度 可以不从 1 开始, 而是找出当前表后部的最长有序子表, 这样可以减少插入的次数,
对 m 个元素的表 T 排序, 找出表尾的最大有序子表 Ts, 此子表长度为 n, 那么只需要且至少需要 m-n 次移动完成排序
将原表表示如下, 括号内为下标序号
E(1), E(2), E(3), ...., X, E(m-n+1), E(m-n+2), E(m-n+3),..., E(m)
X 的序号是 m-n, X 和 E(m-n+1) 是逆序的
E(m-n+1), E(m-n+2), E(m-n+3),..., E(m) 是一个已经达成目标序的最大有序子表
每一次移动操作如果是把首位置元素移动插入到 Ts, 将使 Ts 的长度增 1, 将只需要 m-n 次移动插入即完成排序
如果有任何一次插入目标位置是在 X 的位置之前, 将不可能减少1次插入次数, 因为 X 必须在前面所有元素都移动之后才能移动, 而且 X 也必须移动才能使全表有序(因为 X 在移动之前至少有一个后面位置的元素与其形成逆序关系).
如果有任何一次把元素 Y 插入到目标位置是在 X 相邻的后一位置, 但没有和后面的 Ts 构成一个更大的有序子表, 换言之, 即 Y 与 相邻的后一位置的元素仍是逆序关系, 也一样不能减少 1 次插入次数, 设 Y 移动之前 Ts 的长度已经增长到 L, 那么还至少需要 m-L 次移动插入才能完成排序, 而 Y 的移动并没有增长 Ts 的长度, 那么其移动插入之后, 就一样至少需要 m-L 次移动插入才能完成排序.
综上, 结论: 对 m 个元素的表 T 排序, 找出表尾的最大有序子表 Ts, 此子表长度为 n, 那么只需要且至少需要 m-n 次移动完成排序
算法在开始从表尾向前面逐次对相邻2元素比较, 直到出现逆序, 以此确定表尾的最大长度有序子表, 之后再进行 m-n 次检索移动插入的操作完成全表排序
作者: search_Sudoku 时间: 2019-1-22 22:31
回复 21# 523066680
不同的排序算法自然不能以最好情况下的效率做比较, 而是要以平均效率做比较
我思考这个算法的过程是逆推的:
假设原表T (长度为 L)已是完全有序的, 需要插入的次数是 0
如果不是完全有序的, 就至少要做一次插入操作
在最后一次插入时, 表后部的 长度为 L-1 的子表必然已经是一个完全有序的表(设这个表为T1)
如果 T1 也是由插入操作之后得到的, 那么 去除 那个 插入进来(插入位置包括最前部和最尾部)的元素, 长度为 L-2 的子表 T2 必然也是一个完全有序表,
...
逆推到第一次插入之前, 必然在全表后部存在一个完全有序表, 这个完全有序子表最大长度为 L (也就是全表本来就是完全有序的), 最小长度是 1(表尾后部的两个元素是逆序的)
作者: 老刘1号 时间: 2019-1-23 00:09
回复 17# 523066680
学习了,又秒杀我那个无脑挪法……(固定挪表长+1次)
不知有没有最佳方案,明天再想想
论坛真是藏龙卧虎,不发点题都不肯出来
作者: 523066680 时间: 2019-1-23 08:53
本帖最后由 523066680 于 2019-1-23 12:47 编辑
回复 24# 老刘1号
我先用递归跑出来一些结果,然后去强行找规律、强行解释一波,发现没什么大的漏洞就拿来用了。
有明确的调换方法,假设要调换列标3和7
012345678 - 将7和3位置调换,可以预见的是3和7调换后,012在7的前面(成为7的前缀);456同理;
012345601278 - 将排头的012依次插入7的前方。
0003456012738 - 0127已经达成,现在开头的是数字3,将它放在数字7后面
0000456012745638 - 然后将前缀456插入数字3的前面。
有了这个规则,就可以来一波语法糖- my @index = qw/0 1 2 3 4 5 6 7 8/;
- my ($a, $b) = (3,7);
- my ($apos, $bpos) = ($a, $b);
- my $show = sub {printf "[%d,%d] %s\n", $apos, $bpos, join(",", @index)};
-
- $show->();
- # $a 的前缀转移到 $b 前面
- grep { splice( @index, $bpos-1, 0, shift @index ); $apos--; $show->() } (1..$a);
-
- # $a 移到 $b 后面
- splice( @index, $bpos, 0, shift @index );
- $apos = $bpos, $bpos--;
- $show->();
-
- # $b 的前缀转移到 $a 前面
- grep { splice( @index, $apos-1, 0, shift @index ); $bpos--; $show->() } ($a+1..$b-1);
- exit;
复制代码
- [3,7] 0,1,2,3,4,5,6,7,8
- [2,7] 1,2,3,4,5,6,0,7,8
- [1,7] 2,3,4,5,6,0,1,7,8
- [0,7] 3,4,5,6,0,1,2,7,8
- [7,6] 4,5,6,0,1,2,7,3,8
- [7,5] 5,6,0,1,2,7,4,3,8
- [7,4] 6,0,1,2,7,4,5,3,8
- [7,3] 0,1,2,7,4,5,6,3,8
复制代码
.
回复 26# search_Sudoku
针对 3,4,7,1,10,9,8,11,24(24暂时用K替代) 昨晚拿着扑克牌手动排了几次,处理方式和你的描述接近但有差异,
差不多7次。晚点儿再实践实践
又想了想,递归也可以优化优化(暴力解虽然费时,但是省心 )。
作者: search_Sudoku 时间: 2019-1-23 10:11
回复 21# 523066680
http://www.bathome.net/redirect. ... 1910&pid=217050
22楼, 我做了一个不太严谨的证明
作者: 老刘1号 时间: 2019-1-23 11:19
回复 22# search_Sudoku
看懂了,谢大佬
作者: 老刘1号 时间: 2019-1-23 12:00
本帖最后由 老刘1号 于 2019-1-23 12:09 编辑
回复 25# 523066680
简单明了、高效,学习了。
我的思路(调换3与7)
12345678 - 不想太多,操作3与7就等到能操作的时候再操作,其他按顺序塞到后面
1234567812 - 此时所用步数记作step1
345673812 - 3与7互换,∴先将3放到7后面
45673812#456 - 继续无脑挪到7可操作,步数记为step2
738127456 - 将7放到原先3的位置(4之前)
3812745638 - 恢复原顺序,步数记为step3
由于一波操作下来除了3其它数字都移动一次,而3移动2次,∴总步数为表长+1
下面默认数组下标从1开始,
step1=%第一个数下标(3)%-1
step2=(%第二个数下标(7)%-%第一个数下标(3)%)-1
#一直固定在最开始第一个数(3)后面一个数(4)的前面
∴要计算第二个数(7)需要移动到的位置,就计算"最开始第一个数后面的一个数(4)“的位置。
由于前面每次移动都必然会移动到4的后面,∴4的新下标=4的原下标-步数+表长=(3的原下标+1)-(step1+1+step2)+表长
∴第二个数(7)需要移动到”4的新下标-1“这个下标代表的数的后面,即"(3的原下标+1)-(step1+1+step2)+表长-1"
step3=总步数-已走步数=(表长+1)-(step1+1+step2+1)
真蛋疼
作者: 523066680 时间: 2019-1-23 15:12
本帖最后由 523066680 于 2019-1-23 16:33 编辑
回复 22# search_Sudoku
按这个方式实现了(二分搜索没做进去),很利落- =info
- Ref:
- http://www.bathome.net/redirect.php?goto=findpost&ptid=51910&pid=217048
- =cut
-
- my @arr = qw/25 23 4 9 8 2 1 12 15/;
- my $offset;
- my $first;
- printf "%s <- Original\n", join(",", @arr);
-
- # 查找末尾有序数组的起点
- $offset = $#arr+1 - seqsizes( \@arr );
-
- while ( $offset > 0 || $arr[0] > $arr[1] )
- {
- $first = shift @arr;
- insert( \@arr, $offset, $first );
- $offset--;
- printf "%s\n", join(",", @arr);
- }
-
- sub insert
- {
- my ($arr, $begin, $ele) = @_;
- for my $i ( $begin .. $#$arr ) {
- if ($arr->[$i] > $ele ) { splice( @$arr, $i, 0, $ele ); return; }
- }
- splice( @$arr, $#$arr+1, 0, $ele );
- }
-
- sub seqsizes
- {
- my ($r, $size) = (shift, 1);
- for my $i (1..$#$r) {
- $r->[-$i] > $r->[-$i-1] ? $size++ : last;
- }
- return $size;
- }
复制代码
- 3,4,7,1,10,9,8,11,24 <- Original
- 4,7,1,10,9,3,8,11,24
- 7,1,10,9,3,4,8,11,24
- 1,10,9,3,4,7,8,11,24
- 10,9,1,3,4,7,8,11,24
- 9,1,3,4,7,8,10,11,24
- 1,3,4,7,8,9,10,11,24
复制代码
- 25,23,4,9,8,2,1,12,15 <- Original
- 23,4,9,8,2,1,12,15,25
- 4,9,8,2,1,12,15,23,25
- 9,8,2,1,4,12,15,23,25
- 8,2,1,4,9,12,15,23,25
- 2,1,4,8,9,12,15,23,25
- 1,2,4,8,9,12,15,23,25
复制代码
作者: 老刘1号 时间: 2019-1-23 19:03
本帖最后由 老刘1号 于 2019-1-23 19:26 编辑
回复 22# search_Sudoku
用批实现一个,带二分的之后补上,
在表尾构建有序数列(非二分检索,倒序检索).BAT- @echo off
- Setlocal enabledelayedexpansion
- ::CODER BY 老刘 POWERD BY iBAT
- Set /p List=
- Call :GetList !List!
- Set /A 判断次数=0,挪移次数=0
-
- Rem 确定末尾有序数组的起始位置
- Set /A point=prt-1
- For /l %%a in (!prt! -1 2) Do (
- Set /A SubOne=%%a-1
- Set /A #=.!SubOne!
- Set /A 判断次数+=1
- If !#! Gtr !.%%a! (
- Set /A Point=SubOne
- Goto :JmpOut1
- )
- )
- :JmpOut1
-
- Rem 挪
- For /l %%a in (!Point! -1 1) Do Call :挪 %%a
- Goto :Next
- :挪
- For /l %%b in (!prt! -1 %1) Do (
- Set /A 判断次数+=1
- If !.1! Gtr !.%%b! (
- Call :MoveTo %%b
- Set /a 挪移次数+=1
- Set/p=移到%%b后:<nul
- Call :Echo
- Goto :JmpOut2
- )
- )
- :JmpOut2
- Rem 处理首个元素比末尾有序数组全小的情况
- Set /A 判断次数+=1
- If !.1! Lss !.%1! (
- Call :MoveTo %1
- Set /a 挪移次数+=1
- Set/p=移到%1后:<nul
- Call :Echo
- )
- Goto :Eof
- :Next
-
- Set 判断次数
- Set 挪移次数
- pause&"%~0"
-
- :GetList
- Set /A Prt=0
- Set list=%*
- For %%a In (%*) Do (
- Set /A Prt+=1
- Set .!Prt!=%%a
- )
- Goto :Eof
-
- :MoveTo 移动到原先第%1项的后面
- Set /A #=.1
- For /l %%a in (1 1 %1) Do (
- Set /A PlusOne=%%a+1
- Set /A .%%a=.!PlusOne!
- )
- Set /A .%1=#
- Goto :Eof
-
- :Echo
- For /l %%a in (1 1 !prt!) Do Set/p=!.%%a!-<nul
- Echo.
- Goto :Eof
复制代码
1 3 5 2 4
移到3后:3-5-1-2-4-
移到4后:5-1-2-3-4-
移到5后:1-2-3-4-5-
判断次数=11
挪移次数=3
请按任意键继续. . .
7 3 4 1 10 9 8 11 24
移到6后:3-4-1-10-9-7-8-11-24-
移到5后:4-1-10-9-3-7-8-11-24-
移到5后:1-10-9-3-4-7-8-11-24-
移到4后:10-9-3-1-4-7-8-11-24-
移到7后:9-3-1-4-7-8-10-11-24-
移到6后:3-1-4-7-8-9-10-11-24-
移到2后:1-3-4-7-8-9-10-11-24-
判断次数=38
挪移次数=7
请按任意键继续. . .
25 23 4 9 8 2 1 12 15
移到9后:23-4-9-8-2-1-12-15-25-
移到8后:4-9-8-2-1-12-15-23-25-
移到5后:9-8-2-1-4-12-15-23-25-
移到5后:8-2-1-4-9-12-15-23-25-
移到4后:2-1-4-8-9-12-15-23-25-
移到2后:1-2-4-8-9-12-15-23-25-
判断次数=36
挪移次数=6
请按任意键继续. . .
欢迎光临 批处理之家 (http://bathome.net./) |
Powered by Discuz! 7.2 |