排列组合两种递归写法的效率比较
查了一下论坛,很少有关 SPL 递归的文章。这里用 SPL 写了两种 m 选 n 组合的递归写法,模拟深度优先与回溯。写法可能不成熟,因为运行效率不是很理想,但同样的手法,在 JavaScript,VBA 和 Python 中的运行效率是很高的,甚至在 Power Query 里的效率也很理想。所以,想得到 SPL 大佬的指点,看能不能优化这两种写法,或者有没有其他的写法可以让排列组合的执行效率提高。此贴主要讨论组合的写法,因为组合可以无序,相对不那么复杂。场景很简单,从 m 个数中选出 n 个数,得到不重复组合。为了方便剪枝,例子中用到的数组(序列),都是升序排列的。此贴中就用 20 选 5 来做例子,因为 20.() 序列中没有重复的数字出现,所以没有不重复剪枝发生,但语句中还是写了剪枝方法。
第一种写法,模拟深度优先。IDE 里的语句如下:
A | B | C | |
1 | >a=20.() | >n=5 | |
2 | func | ||
3 | if B2==0 | return [[]] | |
4 | return A2.(if(~==~[-1],[],func(A2,A2.m(#+1:),B2-1)).(get(1)|~)).conj() | ||
5 | >t=now() | ||
6 | =func(A2,a,n) | ||
7 | >output(a.len()/"个数中选出"/n/"个数") | ||
8 | >output("生成"/A6.len()/"个不重复组合") | ||
9 | >output("耗时"/interval@ms(t,now())/"毫秒") |
运行之后可以看到,这个写法的效率还是可以的。本机测得,20 选 5 生成 15504 个不重复组合
耗时 218 毫秒。如果是 26 选 7,甚至是 35 选 7,那就是另一种速度了。
第二种写法,模拟回溯。这个是 SPL 里最迷惑的写法,照理这个写法的效率是很高的,在其他语言环境中的运行效率很高。但在 SPL 里却是出奇的慢。IDE 里的语句如下:
A | B | C | D | |
1 | >arr=20.() | >len=arr.len() | >n=5 | |
2 | [] | >path=[] | // m 选 n | |
3 | func | |||
4 | if path.len()==n | >A2|=[path.m(:)] | //path 要复制一份 | |
5 | for A3,len-n+path.len()+1 | >v=arr.m(B5) | ||
6 | if v==null | return | ||
7 | if B5>A3 && v==arr.m(B5-1) | next B5 | ||
8 | >path|=v | |||
9 | >func(A3,B5+1) | // 注意此处入参是 B5+1, 不是 A3+1 | ||
10 | >path.delete(-1) | |||
11 | return A2 | clear A1:C2 | ||
12 | >t=now() | |||
13 | =func(A3,1) | |||
14 | >output(len/"个数中选出"/n/"个数") | |||
15 | >output("生成"/A13.len()/"个不重复组合") | |||
16 | >output("耗时"/interval@ms(t,now())/"毫秒") |
运行之后,本机测得,20 选 5 生成 15504 个不重复组合,耗时 15469 毫秒。1 万多个结果耗时 15 秒这个效率不能接受,不知道是哪里有问题,在其他语言环境中,这样的回溯剪枝 20 选 5 都是几十毫秒。
当然,还可以有另一种写法,用 xjoin,加上一定的剪枝,也能得到结果,速度还可以。但这不是递归写法,不在此贴讨论范围。
这种组合有一个实际运用场景,就是凑数,比如 100 个数里选出和等于目标值的所有组合,或者选出 n 个数的值使其和等于目标值。这次就不写了,无非就是加个判断条件和剪枝条件。先看看能不能提高效率,麻烦 SPL 大佬指点一二。
Thx in advance!
这种写法大概用时二三十毫秒
=to(20) >m=A1.len() >n=5
=[] /A2 为结果序列 =to(n) /C2 存放临时的选出结果
=now()
谢谢大佬,你这个写法确实快一些。
我也有两个疑问:
1、语句中满足结果时归集的那一格,>A2.insert(0,[C2.to()]) 如果换成 >A2|=[C2.to()] 效率就会明显下降,20 选 7,本机测试从原来的 560 毫秒左右降到了 2533 毫秒。是不是 insert 的效率比 "|=" 这个操作符的效率要高?那不禁会联想,对于序列序表的操作,哪些操作符或者函数的效率会高一些?比如像 delete 函数,这个函数的效率怎么样?或者说事先建好有长度的序列或者有结构的空序表再去进行增删改对提升效率是有助益的?
2、这个写法要如何剪枝,是在程序过程中的剪枝,不是得到结果后的去重。比如换个场景 [1,1,2,2,3] 从这 5 个数里选出 3 个数的不重复组合,得到[1,1,2],[1,1,3],[1,2,2],[1,2,3],[2,2,3]5 个结果。
insert 是修改源序列,| 是产生新序列,序列成员数较多的时候复制数组比较花时间
谢谢大佬指点。
| 操作符会产生新序列,这个知识点在其它语言中也有,也是会影响效率。比如 PowerQuery 里的 & 合并 list,python 中貌似也有同样的。
在你写的基础上,加了两个去重的剪枝条件。