当前位置:文档之家› 排 列 组 合 公 式 及 排 列 组 合 算 法 ( 2 0 2 0 )

排 列 组 合 公 式 及 排 列 组 合 算 法 ( 2 0 2 0 )

字符串的排列组合算法合集全排列在笔试面试中很热门,因为它难度适中,既可以考察递归实现,又能进一步考察非递归的实现,便于区分出考生的水平。

所以在百度和迅雷的校园招聘以及程序员和软件设计师的考试中都考到了,因此本文对全排列作下总结帮助大家更好的学习和理解。

对本文有任何补充之处,欢迎大家指出。

首先来看看题目是如何要求的(百度迅雷校招笔试题)。

一、字符串的排列用C++写一个函数, 如 Foo(const char *str), 打印出 str 的全排列,如 abc 的全排列: abc, acb, bca, dac, cab, cba一、全排列的递归实现为方便起见,用123来示例下。

123的全排列有123、132、213、231、312、321这六种。

首先考虑213和321这二个数是如何得出的。

显然这二个都是123中的1与后面两数交换得到的。

然后可以将123的第二个数和每三个数交换得到132。

同理可以根据213和321来得231和312。

因此可以知道——全排列就是从第一个数字起每个数分别与它后面的数字交换。

找到这个规律后,递归的代码就很容易写出来了:view plaincopy#includeiostream?using?namespace?std;?#includeassert.h?v oid?Permutation(char*?pStr,?char*?pBegin)?{?assert(pStr?pBegin);?if(*pBegin?==?'0')?printf("%s",pStr);?else?{?for(char *?pCh?=?pBegin;?*pCh?!=?'0';?pCh++)?{?swap(*pBegin,*pCh);?P ermutation(pStr,?pBegin+1);?swap(*pBegin,*pCh);?}?}?}?int?m ain(void)?{?char?str[]?=?"abc";?Permutation(str,str);?retur n?0;?}?另外一种写法:view plaincopy--k表示当前选取到第几个数,m表示共有多少个数?void?Permutation(char*?pStr,int?k,int?m)?{?assert(pStr); if(k==m){staticintnum=1;--局部静态变量,用来统计全排列的个数?printf("第%d个排列t%s",num++,pStr);?}?else?{?for(int?i?=?k;?i?=?m;?i++)?{?swa p(*(pStr+k),*(pStr+i));?Permutation(pStr,?k?+?1?,?m);?swap( *(pStr+k),*(pStr+i));?}?}?}?int?main(void)?{?char?str[]?=?" abc";?Permutation(str?,?0?,?strlen(str)-1);?return?0;?}?如果字符串中有重复字符的话,上面的那个方法肯定不会符合要求的,因此现在要想办法来去掉重复的数列。

二、去掉重复的全排列的递归实现由于全排列就是从第一个数字起每个数分别与它后面的数字交换。

我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。

如122,第一个数与后面交换得212、221。

然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。

与由122中第一个数与第三个数交换所得的221重复了。

所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。

再考虑212,它的第二个数与第三个数交换可以得到解决221。

此时全排列生成完毕。

这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。

下面给出完整代码:view plaincopy#includeiostream?using?namespace?std;?#includeassert.h?--在[nBegin,nEnd)区间中是否有字符与下标为pEnd的字符相等?bool?IsSwap(char*?pBegin?,?char*?pEnd)?{?char?*p;?for(p? =?pBegin?;?p?pEnd?;?p++)?{?if(*p?==?*pEnd)?return?false;?}? return?true;?}?void?Permutation(char*?pStr?,?char?*pBegin)? {?assert(pStr);?if(*pBegin?==?'0')?{?static?int?num?=?1;?--局部静态变量,用来统计全排列的个数?printf("第%d个排列t%s",num++,pStr);?}?else?{?for(char?*pCh?=?pBegin;?*pCh?!=? '0';?pCh++)?--第pBegin个数分别与它后面的数字交换就能得到新的排列?{?if(IsSwap(pBegin?,?pCh))?{?swap(*pBegin?,?*pCh);?Permu tation(pStr?,?pBegin?+?1);?swap(*pBegin?,?*pCh);?}?}?}?}?int?main(void)?{?char?str[]?=?"baa";?Permutation(str?,?str);? return?0;?}?OK,到现在我们已经能熟练写出递归的方法了,并且考虑了字符串中的重复数据可能引发的重复数列问题。

那么如何使用非递归的方法来得到全排列了?三、全排列的非递归实现要考虑全排列的非递归实现,先来考虑如何计算字符串的下一个排列。

如"1234"的下一个排列就是"1243"。

只要对字符串反复求出下一个排列,全排列的也就迎刃而解了。

如何计算字符串的下一个排列了?来考虑"926520"这个字符串,我们从后向前找第一双相邻的递增数字,"20"、"52"都是非递增的,"26 "即满足要求,称前一个数字2为替换数,替换数的下标称为替换点,再从后面找一个比替换数大的最小数(这个数必然存在),0、2都不行,5可以,将5和2交换得到"956220",然后再将替换点后的字符串"6220"颠倒即得到"950226"。

对于像“4321”这种已经是最“大”的排列,采用STL中的处理方法,将字符串整个颠倒得到最“小”的排列"1234"并返回false。

view plaincopy#includeiostream?#includealgorithm?#includecstring?using namespacestd;#includeassert.h--反转区间?void?Reverse(char*?pBegin?,?char*?pEnd)?{?while(pBegin?p End)?swap(*pBegin++?,?*pEnd--);?}?--下一个排列?bool?Next_permutation(char?a[])?{?assert(a);?char?*p?,?*q?,?*pFind;?char?*pEnd?=?a?+?strlen(a)?-?1;?if(a?==?pEnd)?r eturn?false;?p?=?pEnd;?while(p?!=?a)?{?q?=?p;?p--;?if(*p?*q )?--找降序的相邻2数,前一个数即替换数?{?--从后向前找比替换点大的第一个数?pFind?=?pEnd;?while(*pFind?*p)?--pFind;?swap(*p?,?*pFind );?--替换点后的数全部反转?Reverse(q?,?pEnd);?return?true;?}?}?Reverse(a?,?pEnd);?--如果没有下一个排列,全部反转后返回false?return?false;?}?int?cmp(const?void?*a,const?void?*b)? {?return?int(*(char?*)a?-?*(char?*)b);?}?int?main(void)?{?c har?str[]?=?"bac";?int?num?=?1;?qsort(str?,?strlen(str),siz eof(char),cmp);?do?{?printf("第%d个排列t%s",num++,str);?}while(Next_permutation(str));?return?0;?}至此我们已经运用了递归与非递归的方法解决了全排列问题,总结一下就是:1、全排列就是从第一个数字起每个数分别与它后面的数字交换。

2、去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。

3、全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。

二、字符串的组合题目:输入一个字符串,输出该字符串中字符的所有组合。

举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。

上面我们详细讨论了如何用递归的思路求字符串的排列。

同样,本题也可以用递归的思路来求字符串的组合。

view plaincopy#includeiostream?#includevector?#includecstring?using?na mespace?std;?#includeassert.h?void?Combination(char?*string ,intnumber,vectorcharresult);voidCombination(char*str ing)?{?assert(string?!=?NULL);?vectorchar?result;?int?i?,?l ength?=?strlen(string);?for(i?=?1?;?i?=?length?;?++i)?Combi nation(string?,?i?,result);?}?void?Combination(char?*string ,intnumber,vectorcharresult){assert(string!=NULL); if(number==0){staticintnum=1;printf("第%d个组合t",num++);?vectorchar::iterator?iter?=?result.begin();?for( ;iter!=result.end();++iter)printf("%c",*iter);print f("");?return?;?}?if(*string?==?'0')?return?;?result.push_b ack(*string);?Combination(string?+?1?,?number?-?1?,?result) ;?result.pop_back();?Combination(string?+?1?,?number?,?resu lt);?}?int?main(void)?{?char?str[]?=?"abc";?Combination(str );?return?0;?}?由于组合可以是1个字符的组合,2个字符的字符……一直到n个字符的组合,因此在函数void Combination(char* string),我们需要一个for循环。

相关主题