來源http://blog.csdn.net/wuzhekai1985
問題1 :輸入一個字符串,打印出該字符串中字符的所有排列。例如輸入字符串abc,則輸出由字符a、b、c所能排列出來的所有字符串abc、acb、bac、bca、cab和cba。
思路:這是個遞歸求解的問題。遞歸算法有四個特性:(1)必須有可達到的終止條件,否則程序將陷入死循環;(2)子問題在規模上比原問題小;(3)子問題可通過再次遞歸調用求解;(4)子問題的解應能組合成整個問題的解。
對於字符串的排列問題。如果能生成n - 1個元素的全排列,就能生成n個元素的全排列。對於只有1個元素的集合,可以直接生成全排列。全排列的遞歸終止條件很明確,只有1個元素時。下面這個圖很清楚的給出了遞歸的過程。
參考代碼:解法1通過Permutation_Solution1(str, 0, n); 解法2通過調用Permutation_Solution2(str, str)來求解問題。
1 //函數功能 : 求一個字符串某個區間內字符的全排列 2 //函數參數 : pStr為字符串,begin和end表示區間 3 //返回值 : 無 4 void Permutation_Solution1(char *pStr, int begin, int end) 5 { 6 if(begin == end - 1) //只剩一個元素 7 { 8 for(int i = 0; i < end; i++) //打印 9 cout<<pStr[i]; 10 cout<<endl; 11 } 12 else 13 { 14 for(int k = begin; k < end; k++) 15 { 16 swap(pStr[k], pStr[begin]); //交換兩個字符 17 Permutation_Solution1(pStr, begin + 1, end); 18 swap(pStr[k],pStr[begin]); //恢復 19 } 20 } 21 } 22 23 //函數功能 : 求一個字符串某個區間內字符的全排列 24 //函數參數 : pStr為字符串,pBegin為開始位置 25 //返回值 : 無 26 void Permutation_Solution2(char *pStr, char *pBegin) 27 { 28 if(*pBegin == '\0') 29 { 30 cout<<pStr<<endl; 31 } 32 else 33 { 34 char *pCh = pBegin; 35 while(*pCh != '\0') 36 { 37 swap(*pBegin, *pCh); 38 Permutation_Solution2(pStr, pBegin + 1); 39 swap(*pBegin, *pCh); 40 pCh++; 41 } 42 } 43 } 44 //提供的公共接口 45 void Permutation(char *pStr) 46 { 47 Permutation_Solution1(pStr, 0, strlen(pStr)); 48 //Permutation_Solution2(pStr,pStr); 49 }
問題2:輸入一個字符串,輸出該字符串中字符的所有組合。舉個例子,如果輸入abc,它的組合有a、b、c、ab、ac、bc、abc。
思路:同樣是用遞歸求解。可以考慮求長度為n的字符串中m個字符的組合,設為C(n,m)。原問題的解即為C(n, 1), C(n, 2),...C(n, n)的總和。對於求C(n, m),從第一個字符開始掃描,每個字符有兩種情況,要么被選中,要么不被選中,如果被選中,遞歸求解C(n-1, m-1)。如果未被選中,遞歸求解C(n-1, m)。不管哪種方式,n的值都會減少,遞歸的終止條件n=0或m=0。
1 //函數功能 : 從一個字符串中選m個元素 2 //函數參數 : pStr為字符串, m為選的元素個數, result為選中的 3 //返回值 : 無 4 void Combination_m(char *pStr, int m, vector<char> &result) 5 { 6 if(pStr == NULL || (*pStr == '\0'&& m != 0)) 7 return; 8 if(m == 0) //遞歸終止條件 9 { 10 for(unsigned i = 0; i < result.size(); i++) 11 cout<<result[i]; 12 cout<<endl; 13 return; 14 } 15 //選擇這個元素 16 result.push_back(*pStr); 17 Combination_m(pStr + 1, m - 1, result); 18 result.pop_back(); 19 //不選擇這個元素 20 Combination_m(pStr + 1, m, result); 21 } 22 //函數功能 : 求一個字符串的組合 23 //函數參數 : pStr為字符串 24 //返回值 : 無 25 void Combination(char *pStr) 26 { 27 if(pStr == NULL || *pStr == '\0') 28 return; 29 int number = strlen(pStr); 30 for(int i = 1; i <= number; i++) 31 { 32 vector<char> result; 33 Combination_m(pStr, i, result); 34 } 35 }
問題3:打靶問題。一個射擊運動員打靶,靶一共有10環,連開10 槍打中90環的可能性有多少?
思路:這道題的思路與字符串的組合很像,用遞歸解決。一次射擊有11種可能,命中1環至10環,或脫靶。
參考代碼:
1 //函數功能 : 求解number次打中sum環的種數 2 //函數參數 : number為打靶次數,sum為需要命中的環數,result用來保存中間結果,total記錄種數 3 //返回值 : 無 4 void ShootProblem_Solution1(int number, int sum, vector<int> &result, int *total) 5 { 6 if(sum < 0 || number * 10 < sum) //加number * 10 < sum非常重要,它可以減少大量的遞歸,類似剪枝操作 7 return; 8 if(number == 1) //最后一槍 9 { 10 if(sum <= 10) //如果剩余環數小於10,只要最后一槍打sum環就可以了 11 { 12 for(unsigned i = 0; i < result.size(); i++) 13 cout<<result[i]<<' '; 14 cout<<sum<<endl; 15 (*total)++; 16 return; 17 } 18 else 19 return; 20 } 21 for(unsigned i = 0; i <= 10; i++) //命中0-10環 22 { 23 result.push_back(i); 24 ShootProblem_Solution1(number-1, sum-i, result, total); //針對剩余環數遞歸求解 25 result.pop_back(); 26 } 27 } 28 //提供的公共接口 29 void ShootProblem(int number, int sum) 30 { 31 int total = 0; 32 vector<int> result; 33 ShootProblem_Solution1(number, sum, result, &total); 34 cout<<"total nums = "<<total<<endl; 35 }