全排列(含递归和非递归的解法)
全排列在近幾年各大網(wǎng)絡(luò)公司的筆試中出現(xiàn)的比較頻繁
首先來(lái)看看題目是如何要求的(百度迅雷校招筆試題)。
用C++寫(xiě)一個(gè)函數(shù), 如 Foo(const char *str), 打印出 str 的全排列,
如 abc 的全排列: abc, acb, bca, dac, cab, cba
一、??????遞歸版本
1、算法簡(jiǎn)述
簡(jiǎn)單地說(shuō):就是第一個(gè)數(shù)分別以后面的數(shù)進(jìn)行交換
E.g:E = (a , b , c),則 prem(E)= a.perm(b,c)+ b.perm(a,c)+ c.perm(a,b)
然后a.perm(b,c)= ab.perm(c)+ ac.perm(b)= abc + acb.依次遞歸進(jìn)行
?
好了,知道算法之后就不難編出一份好的代碼了。
2、代碼參考
1 Foo(const char *str) 2 { 3 Perm( str , 0 , strlen( str ) – 1 ); 4 } 1 //需要三個(gè)參數(shù),k表示當(dāng)前的數(shù),m表示數(shù)的個(gè)數(shù) 2 Perm( char *pszStr , int k , int m ) 3 { 4 if (k == m) 5 { 6 static int s_i = 1; 7 cout<<” 第 ”<<s_i ++<<” 個(gè)排列 ”<<pszStr<<endl; 8 } 9 else 10 { 11 for (int i = k; i <= m; i++) //第i個(gè)數(shù)分別與它后面的數(shù)字交換就能得到新的排列 12 { 13 Swap(pszStr + k, pszStr + i); 14 Perm(pszStr, k + 1, m); 15 Swap(pszStr + k, pszStr + i); 16 } 17 } 18 }3、見(jiàn)圖知曉
不過(guò)這樣存在一點(diǎn)小小的缺陷:兩個(gè)相同的數(shù)也進(jìn)行了交換,見(jiàn)下圖:
明顯,這絕對(duì)不符合要求:
4、代碼改進(jìn)
去掉重復(fù)符號(hào)的全排列:在交換之前可以先判斷兩個(gè)符號(hào)是否相同,不相同才交換,這個(gè)時(shí)候需要一個(gè)判斷符號(hào)是否相同的函數(shù)。
bool IsSwap(char *pszStr, int nBegin, int nEnd) { for (int i = nBegin; i < nEnd; i++) if (pszStr[i] == pszStr[nEnd]) return false; return true; }所以,改進(jìn)的代碼如下:
1 Perm(char *pszStr, int k, int m) 2 { 3 if (k == m) 4 { 5 Static int s_i = 1; 6 cout<<” 第 ”<<s_i ++<<” 個(gè)排列 ”<<pszStr<<endl; 7 } 8 else 9 { 10 for (int i = k; i <= m; i++) //第i個(gè)數(shù)分別與它后面的數(shù)字交換就能得到新的排列 11 { 12 if (IsSwap(pszStr, k, i)) //添加的判斷語(yǔ)句,判斷是否相等 13 { 14 Swap(pszStr + k, pszStr + i); 15 Perm(pszStr, k + 1, m); 16 Swap(pszStr + k, pszStr + i); 17 } 18 } 19 } 20 }OK,見(jiàn)圖知情況
?二、?非遞歸版本
1、算法簡(jiǎn)述
要考慮全排列的非遞歸實(shí)現(xiàn),先來(lái)考慮如何計(jì)算字符串的下一個(gè)排列。如"1234"的下一個(gè)排列就是"1243"。只要對(duì)字符串反復(fù)求出下一個(gè)排列,全排列的也就迎刃而解了。
如何計(jì)算字符串的下一個(gè)排列了?來(lái)考慮"926520"這個(gè)字符串,我們從后向前找第一雙相鄰的遞增數(shù)字,"20"、"52"都是非遞增的,"26 "即滿足要求,稱前一個(gè)數(shù)字2為替換數(shù),替換數(shù)的下標(biāo)稱為替換點(diǎn),再?gòu)暮竺嬲乙粋€(gè)比替換數(shù)大的最小數(shù)(這個(gè)數(shù)必然存在),0、2都不行,5可以,將5和2交換得到"956220",然后再將替換點(diǎn)后的字符串"6220"顛倒即得到"950226"。
如果達(dá)到這個(gè)數(shù)的最大,比如1234-à4321,這個(gè)時(shí)候就結(jié)束整個(gè)循環(huán)。
如果輸入是一個(gè)非最小數(shù),如1324,則將它轉(zhuǎn)換為最小數(shù),如1234,再進(jìn)行排序。排序算法用快排,可以自己寫(xiě)一個(gè),如果快排不會(huì)的話,就先看會(huì)再來(lái)接著看,或者自己想一個(gè)靠譜的算法,也可以直接用VC庫(kù)中的qsort(s , n , sizeof(s[0]) , cmp);各參數(shù)是什么意思就自己在下面多花點(diǎn)時(shí)間吧。
OK,下面看代碼分析
2、代碼分析
1 Prem( char *s ) //全排列函數(shù) 2 { 3 char *pEnd = s + strlen(s) - 1; 4 char *p = pEnd; //p代表替換點(diǎn) 5 //q代表替換點(diǎn)的下一個(gè)數(shù) ,pMax 代表替換點(diǎn)后比替換點(diǎn)大的最小數(shù) 6 char *q = new char,*pMax = new char; //注意初始化!!! 7 while (p != s) //p == s 就結(jié)束循環(huán) 8 { 9 q = p; 10 p--; 11 if (*p < *q) 12 { 13 pMax = FindMaxForOne(p,pEnd); //找與替換點(diǎn)交換的點(diǎn) 14 Swap(p,pMax); //交換 15 Reverse(q,pEnd); //將替換點(diǎn)后所有數(shù)進(jìn)行反轉(zhuǎn) 16 Print(s); //輸出 17 p = pEnd; //將替換點(diǎn)置最后一個(gè)點(diǎn),開(kāi)始下一輪循環(huán) 18 } 19 if (s == p) break; //結(jié)束條件 20 } 21 }上面涉及到幾個(gè)函數(shù)
說(shuō)一下找與替換數(shù)交換的數(shù)的函數(shù)
1 char* FindMaxForOne(char *p,char *q) 2 { 3 char *p1 = p; 4 char *p2 = q; 5 while (*p2 <= *p1) p2--; 6 return p2; 7 }!!!這里要說(shuō)明:從后往前找的第一個(gè)比替換數(shù)大的數(shù)一定就是要找的最小數(shù),Why,這個(gè)慢慢品味,我做的時(shí)候也遇到一定的困難,自己去做,不經(jīng)歷就不會(huì)輕易銘記。
其他函數(shù)簡(jiǎn)直就是小case了。祝君成功!
3、見(jiàn)圖知曉
?
三、非遞歸還有一種方法
描述:和上一種不同的是:這種算法比較笨,但很好理解,不用按照上一種那么嚴(yán)格從小到大進(jìn)行排列輸出。
首先先將最后一個(gè)數(shù)從右往左依次交換輸出,然后判斷個(gè)數(shù)是否為基數(shù),交換離該數(shù)最遠(yuǎn)端的兩個(gè)數(shù),再把第一個(gè)數(shù)從左往右交換輸出,交換遠(yuǎn)端的兩個(gè)數(shù),如此進(jìn)行循環(huán)就能排列完全部的數(shù)。這說(shuō)得可能比較抽象,看一個(gè)例子:
E.g: ? ? ? ? 1 2 3 4
第一次:(從右往左):1 2 4 3 ? --- ?1 2 4 3 --- 1 4 2 3 ?--- ?4 1 2 3 ? 把最后一個(gè)數(shù)依次往前移
交換:2 和 3 ?---> ? 4 1 3 2
第二次:(從左往右):4 1 3 2 ?--- ?1 4 3 2 ?--- ?1 3 4 2 ?--- ?1 3 2 4 ?把第一個(gè)數(shù)依次往后移
交換:1 和 3 ?----> 3 1 2 4 ? ? ? ? ? 重復(fù)第一次,知道把所有數(shù)輸出為止
看代碼:
1 /************************************************************************ 2 * Author: bakari 3 * Date: 2011.5.7 4 /************************************************************************/ 5 int n; 6 void swap(int *a,int *b); //交換函數(shù) 7 void print(int a[]); //打印交換后的每一組數(shù) 8 int jfc(); //求階乘函數(shù) 9 int jmp(int n); //跳轉(zhuǎn)函數(shù) 10 void sort(int a[]); //全排列函數(shù) 11 12 int main(){ 13 while(cin>>n) 14 { 15 while(n<=0) 16 { 17 cout<<"輸入有誤!請(qǐng)重新輸入: "; 18 cin>>n; 19 } 20 int *a=new int[n]; 21 for(int i=0;i<n;i++) 22 a[i]=i+1; 23 sort(a); 24 delete []a; 25 } 26 system("pause"); 27 return 0; 28 } 29 30 void swap(int *a,int *b) 31 { 32 int t=*a; 33 *a=*b; 34 *b=t; 35 } 36 void print(int a[]) 37 { 38 for(int i=0;i<n;i++) 39 cout<<a[i]<<' '; 40 cout<<endl; 41 42 } 43 int jfc() 44 { 45 int s=1; 46 for(int i=1;i<=n;i++) 47 s*=i; 48 return s; 49 } 50 int jmp(int n) 51 { 52 if(n>jfc()) 53 return 0; 54 } 55 void sort(int a[]) 56 { 57 int m=1,count=0; //m統(tǒng)計(jì)全排列的個(gè)數(shù),count統(tǒng)計(jì)行數(shù) 58 int *p1,*p2; 59 for(p1=a+n-1,p2=a+n-2;p1>=a+1,p2>=a;p1--,p2--) 60 { 61 print(a); 62 swap(p1,p2); 63 m++; 64 } 65 count++; 66 while(m<=jfc()){ 67 if(count%2) 68 { print(a); 69 swap(&a[n-1],&a[n-2]); 70 m++; 71 if(!jmp(m)) 72 break; 73 for(p1=a,p2=a+1;p1<=a+n-2,p2<=a+n-1;p1++,p2++) 74 { 75 print(a); 76 swap(p1,p2); 77 m++; 78 } 79 count++; 80 } 81 else 82 { 83 print(a); 84 swap(&a[0],&a[1]); 85 m++; 86 if(!jmp(m)) 87 break; 88 for(p1=a+n-1,p2=a+n-2;p1>=a+1,p2>=a;p1--,p2--) 89 { 90 print(a); 91 swap(p1,p2); 92 m++; 93 } 94 count++; 95 } 96 97 } 98 cout<<"共有"<<m-1<<"種排列"<<endl; 99 }關(guān)鍵是掌握上面兩種!
?
四、 ??總結(jié)
至此我們已經(jīng)運(yùn)用了遞歸與非遞歸的方法解決了全排列問(wèn)題,總結(jié)一下就是:
1.全排列就是從第一個(gè)數(shù)字起每個(gè)數(shù)分別與它后面的數(shù)字交換。
2.去重的全排列就是從第一個(gè)數(shù)字起每個(gè)數(shù)分別與它后面非重復(fù)出現(xiàn)的數(shù)字交換。
3.全排列的非遞歸就是由后向前找替換數(shù)和替換點(diǎn),然后由后向前找第一個(gè)比替換數(shù)大的數(shù)與替換數(shù)交換,最后顛倒替換點(diǎn)后的所有數(shù)據(jù)。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? -----寫(xiě)于2012/8/2--23:48
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 希望大家多多燒香!
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的全排列(含递归和非递归的解法)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 出现次数超过一半的数字
- 下一篇: 动态规划算法学习