首先从递归实现来看,递归会逐步分解问题,所以比较容易理解,但需要消耗大量的堆栈空间,线程堆栈空间不够就无法执行,函数调用开销也很大。
(1)全排列:
所有数组是指按一定的顺序排列集合中的所有元素,使用p(n,n )=n! 表示n个元素的全部序列的个数。
例如,{1、2、3}的全部排列如下。
123; 132;
213; 231;
312; 321;
一共六个,也就是三个!=321=6。
这个是怎么算出的?
首先取一个元素,例如取出1,就剩下{ 2,3 }。
然后,从剩下的集合中取出一个元素,例如取出2,就剩下了{3}。
这样,如果一次取得所有能想到的情况,就成为如图所示的全部排列。
知道这个过程后,我也写了算法:
将数组视为一个集合,将集合分为0~s和s~e两部分。 其中0到s表示已经选择的元素,s到e表示尚未选择的元素。
按照perm(set,s,e )的顺序调用perm ) set,s 1,e )直到s (从e中选择一个要素并与s进行交换),也就是说,输出set )直到剩下的集合为空
c语言代码如下:
voidperm(intlist[],ints,inte,void ) cbk ) ) intlist[] ) inti; if(se ) ) cbk ) ) list; (else ) for ) I=s; i=e; I ) swap (列表,s,I ); perm(list,s 1,e,cbk ); swap (列表,s,I ); } }
其中:
voidswap(int*o,inti,intj ) {inttmp=o[i]; o[i]=o[j]; o[j]=tmp; } void CBK _ print (int * subs (printf ) ) ) ) ) ) ) ) ); for(inti=0; I
)2)组合:
组合是指从n个不同元素中提取m个元素合成的组,该组中的元素没有顺序。 从n个元素中取出k个元素的取法的数量用c(n,k )表示。
C(N,k )=n! //K! *(n-k )! )
例如,从{1、2、3、4}中取出的2个元素的组合如下。
12; 13; 14; 23; 24; 34
方法是先从集合中取出一个元素,例如取出1,{ 2,3,4 }剩下
然后,从剩下的集合中提取元素。 例如,取出2
此时12构成如图所示的组。
从以上过程可以看出,每次从集合中选择一个要素时,对剩下的集合(n-1 )进行k-1的组合。
COMB(set,subset,n,k ) )相反,从集合中选择一个元素,并将该元素放入subset中。 调用comb(set、subset、n-1、k-1 )直到仅选择一个元素}
c语言代码如下:
voidcombine(ints[],intn,intk,void ) CBK ) int*subset,intk ) ) if ) k==0) ) CBK ) subset,k ); 返回; }for(inti=n; i=k; I--}{subset[k-1]=s[I-1]; if(K1 )组合(s,i-1,k-1,cbk ); }else{cbk(subset,subset_length ); } }
任何递归算法都可以转换为非递归算法。 如果可以使用堆栈模拟函数保存调用中的参数就好了,当然,这样的方法没有什么意义,所以在这里就不讨论了。 这里介绍了用其他思路实现的非递归算法。
(1)全排列:
让我们先看看代码:
# include # includeusingnamespacestd; intmain () intmyints ) )={ 1,2,3 }; 出局了
此代码已通过STL Permutation。 应该注意的是第10行,首先对数组进行了排序。
第14行的next_permutation ()是STL函数,它生成当前列表中的下一个相邻词典序列表,其中的元素具有只能交换位置而不能更改数字的原理。
什么意思?
123的下一个词典顺序是132。 因为132比123大,但比其他顺序小。
算法是:
(1)从右向左,找到小于第一个右边数字的数字a。
)从右向左,找到大于第一个a的数字b。
)3)更换A和B。
)4)反转a之后的串(a除外)。
完成了。
现在,按照以上思路,我们来导出next_permutation函数。
templateboolnext_perm(t*start,T*end ) )/_ASM{int3}if ) start==end ) {returnfalse; }else{T*pA=NULL,*pB; T tmp=*end; //finda.for(t*p=end; p=start; p--}{if(p=start; p--}{if(p*pa ) ) {pB=p; 布雷克; }}//swap A,B.tmp=*pA; *pA=*pB; *pB=tmp; //flipsequenceafterafor (t * p=pa1,*q=end; p
(2)组合) 01交换法
如果使用0或1,则指示选定集合中是否存在元素。 因此,0/1列表可以表示选定的元素。
例如,如果[1 2 3 4 5],所选元素为[1 2 3],则列表为[1110]。
算法如下:
COMB(set,n,k ) ) (1)从左向右扫描0/1列表,遇到“10”组合时,转换为“01”。 )2)将在上一步中找到的“10”组合之前的所有1移动到set的最左边。 (3) (1) )2)重复,直到“10”的组合消失。 }
代码如下。
templatevoidcombine(Tset[] (,intn,intk,void ) CBK (tset ) ) ) unsigned char * vec=newunsignedchar (n ) n ); T*subset=newT[k]; //buildthe0-1vector.for(inti=0; I
对于其原因,n个位置处有k个1,根据算法移动可以确保k个1位置不重叠并且可以被n次复盖。