1.字串的全排列。
全排列就是從第一個數起每個數分別與它後面的數交換。
遞迴實現
從集合中依次選出每一個元素,作為排列的第一個元素,然後對剩餘的元素進行全排列,如此遞迴處理,從而得到所有元素的全排列。以對字串abc進行全排列為例,我們可以這麼做:以abc為例
固定a,求後面bc的排列:abc,acb,求好後,a和b交換,得到bac
固定b,求後面ac的排列:bac,bca,求好後,a和c交換,得到cba
固定c,求後面ba的排列:cba,cab。代碼可如下編寫所示:
template <typename T> void CalcAllPermutation_R(T perm[], int first, int num) { if (num <= 1) { return; } for (int i = first; i < first + num; ++i) { swap(perm[i], perm[first]); CalcAllPermutation_R(perm, first + 1, num - 1); swap(perm[i], perm[first]); } }
或者這樣:
#include<iostream>using namespace std;#include<assert.h>void Permutation(char* pStr, char* pBegin){assert(pStr && pBegin);if(*pBegin == '\0')printf("%s\n",pStr);else{for(char* pCh = pBegin; *pCh != '\0'; pCh++){swap(*pBegin,*pCh);Permutation(pStr, pBegin+1);swap(*pBegin,*pCh);}}}int main(void){char str[] = "abc";Permutation(str,str);return 0;}
字典序排列
把升序排列作為當前排列開始,然後依次計算當前排列的下一個字典序排列。
對當前排列從後向前掃描,找到一對為升序的相鄰元素,記為i和j(i < j)。如果不存在這樣一對為升序的相鄰元素,則所有排列均已找到,演算法結束;否則,重新對當前排列從後向前掃描,找到第一個大於i的元素k,交換i和k,然後對從j開始到結束的子序列反轉,則此時得到的新排列就為下一個字典序排列。這種方式實現得到的所有排列是按字典序有序的,這也是C++
STL演算法next_permutation的思想。演算法實現如下:
template <typename T> void CalcAllPermutation(T perm[], int num) { if (num < 1) return; while (true) { int i; for (i = num - 2; i >= 0; --i) { if (perm[i] < perm[i + 1]) break; } if (i < 0) break; // 已經找到所有排列 int k; for (k = num - 1; k > i; --k) { if (perm[k] > perm[i]) break; } swap(perm[i], perm[k]); reverse(perm + i + 1, perm + num); } }
去掉重複的全排列的遞迴實現
去重的全排列就是從第一個數字起每個數分別與它後面非重複出現的數字交換。
#include<iostream>using namespace std;#include<assert.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\n",num++,pStr);}else{for(char *pCh = pBegin; *pCh != '\0'; pCh++) //第pBegin個數分別與它後面的數字交換就能得到新的排列 {if(IsSwap(pBegin , pCh)){swap(*pBegin , *pCh);Permutation(pStr , pBegin + 1);swap(*pBegin , *pCh);}}}}int main(void){char str[] = "baa";Permutation(str , str);return 0;}
2.字串的組合。
假設我們想在長度為n的字串中求m個字元的組合。我們先從頭掃描字串的第一個字元。針對第一個字元,我們有兩種選擇:第一是把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選取m-1個字元;第二是不把這個字元放到組合中去,接下來我們需要在剩下的n-1個字元中選擇m個字元。這兩種選擇都很容易用遞迴實現。下面是這種思路的參考代碼:
#include<iostream>#include<vector>#include<cstring>using namespace std;#include<assert.h>void Combination(char *string ,int number,vector<char> &result);void Combination(char *string){assert(string != NULL);vector<char> result;int i , length = strlen(string);for(i = 1 ; i <= length ; ++i)Combination(string , i ,result);}void Combination(char *string ,int number , vector<char> &result){assert(string != NULL);if(number == 0){static int num = 1;printf("第%d個組合\t",num++);vector<char>::iterator iter = result.begin();for( ; iter != result.end() ; ++iter)printf("%c",*iter);printf("\n");return ;}if(*string == '\0')return ;result.push_back(*string);Combination(string + 1 , number - 1 , result);result.pop_back();Combination(string + 1 , number , result);}int main(void){char str[] = "abc";Combination(str);return 0;}
或者這樣
#include<iostream>using namespace std;int a[] = {1,3,5,4,6};char str[] = "abcde";void print_subset(int n , int s){printf("{");for(int i = 0 ; i < n ; ++i){if( s&(1<<i) ) // 判斷s的二進位中哪些位為1,即代表取某一位printf("%c ",str[i]); //或者a[i]}printf("}\n");}void subset(int n){for(int i= 0 ; i < (1<<n) ; ++i){print_subset(n,i);}}int main(void){subset(5);return 0;}
3.八皇后問題
由於八個皇后的任意兩個不能處在同一行,那麼這肯定是每一個皇后佔據一行。於是我們可以定義一個數組ColumnIndex[8],數組中第i個數字表示位於第i行的皇后的列號。先把ColumnIndex的八個數字分別用0-7初始化,接下來我們要做的事情就是對數組ColumnIndex做全排列。由於我們是用不同的數字初始化數組中的數字,因此任意兩個皇后肯定不同列。我們只需要判斷得到的每一個排列對應的八個皇后是不是在同一對角斜線上,也就是數組的兩個下標i和j,是不是i-j==ColumnIndex[i]-Column[j]或者j-i==ColumnIndex[i]-ColumnIndex[j]。
#include<iostream>using namespace std;int g_number = 0;void Permutation(int * , int , int );void Print(int * , int );void EightQueen( ){const int queens = 8;int ColumnIndex[queens];for(int i = 0 ; i < queens ; ++i)ColumnIndex[i] = i; //初始化Permutation(ColumnIndex , queens , 0);}bool Check(int ColumnIndex[] , int length){int i,j;for(i = 0 ; i < length; ++i){for(j = i + 1 ; j < length; ++j){if( i - j == ColumnIndex[i] - ColumnIndex[j] || j - i == ColumnIndex[i] - ColumnIndex[j]) //在正、副對角線上return false;}}return true;}void Permutation(int ColumnIndex[] , int length , int index){if(index == length){if( Check(ColumnIndex , length) ) //檢測棋盤當前的狀態是否合法{++g_number;Print(ColumnIndex , length);}}else{for(int i = index ; i < length; ++i) //全排列{swap(ColumnIndex[index] , ColumnIndex[i]);Permutation(ColumnIndex , length , index + 1);swap(ColumnIndex[index] , ColumnIndex[i]);}}}void Print(int ColumnIndex[] , int length){printf("%d\n",g_number);for(int i = 0 ; i < length; ++i)printf("%d ",ColumnIndex[i]);printf("\n");}int main(void){EightQueen();return 0;}
4.輸入兩個整數n和m,從數列1,2,3...n中隨意取幾個數,使其和等於m,要求列出所有的組合。
#include <iostream>#include <list>using namespace std;list<int> list1;void find_factor(int sum,int n){//遞迴出口if(n<=0||sum<=0)return;//輸出找到的數if(sum==n){list1.reverse();for(list<int>::iterator iter=list1.begin();iter!=list1.end();iter++)cout<<*iter<<"+";cout<<n<<endl;list1.reverse();}list1.push_front(n);find_factor(sum-n,n-1);//n放在裡面list1.pop_front();find_factor(sum,n-1);//n不放在裡面}int main(void){int sum,n;cin>>sum>>n;cout<<"所有可能的序列,如下:"<<endl;find_factor(sum,n);return 0;}