题目:输入一个字符串,打印出该字符串中字符放入所有排列。例如输入字符串abc,则打印出字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
思路:全排列的问题实际就是交换问题。把问题划分成两个部分,第一个字符和后面的部分,所以这个问题分为两个步骤:(1)考虑第一个字符的所有可能情况,将第一个字符和后面的所有字符交换;(2)考虑第一个字符后面的整体部分,那么这部分又可以分成两个部分,递归的调用前面的代码。
非递归算法见:http://blog.csdn.net/moses1213/article/details/51058053
void Permutation(char* pStr) { if(pStr == NULL) return; Permutation(pStr, pStr) } Permutation(char* pStr, char* pBegin) { if(*pBegin == '\0') cout << pStr << endl; else { for(char* pCh = pBegin; *pCh != '\0'; ++pCh) { char temp = *pCh; *pCh = *pBegin; *pBegin = temp; Permutation(pStr, pBegin + 1); temp = *pCh; *pCh = *pBegin; *pBegin = temp; } } }
上面的算法只适用于没有重复输入的情况,对于存在重复的字符例如“abb"会输出六种结果,其中有重复的,正确的结果应该有(3!)/(2!) = 3种,3!表示3的阶乘。下面采用一种新的算法:为了避免重复交换,每次检查与首字符交换的的字符是否已经交换过,判断的过程是从头指针一直遍历到当前交换字符的指针,如果指向的值相等,代表当前字符已经与首字符交换过,直接跳过当前字符。例如"abb",a与第二个b交换后为"bab",a与第三个b交换的时候因为b之前已经有一个b,这种情况直接跳到下一步骤。
//不重复版本 bool HasBeSwaped(char* pBegin, char* pCurrent) { for(char* pCh = pBegin; pCh != pCurrent; ++pCh) { if(*pCh == *pCurrent) return true; } return false; } void Permutation(char* pStr, char* pBegin) { if(*pBegin == '\0') cout << pStr << endl; else { for(char* pCh = pBegin; *pCh != '\0'; ++pCh) { if(HasBeSwaped(pBegin, pCh) continue; char temp = *pCh; *pCh = *pBegin; *pBegin = temp; Permutation(pStr, pBegin + 1); temp = *pCh; *pCh = *pBegin; *pBegin = temp; } } } void Permutation(char* pStr) { if(pStr == NULL) return; Permutation(pStr, pStr); }
问题扩展:字符的组合问题
输入三个字符a、b、c,则他们的组合有a、b、c、ab、ac、bc、abc。如果输入n个字符,则这n个字符能构成长度为1的组合、长度为2的组合、……、长度为n的组合。在求n个字符长度为m(1<=m<=n)的组合的时候,我们把这n个字符划成两部分:第一个字符和其余所有字符。如果组合里包含第一个字符,则下一步在剩余的字符里选取m-1个字符;如果组合里不包含第一个字符,则下一步在剩余的n-1个字符里选取m个字符。也就是说,我们可以把求n个字符组成长度为m的组合的问题分解成两个子问题,分别求n-1个字符串中长度为m-1的组合,以及求n-1个字符的长度为m的组合。这两个子问题都可以用递归的方式解决。
代码实现:
void Combination(char* pStr, size_t size, vector<char> result) { if(strlen(pStr) < size || size < 0) return; //已经达到要求长度,输出 if(size == 0) { for(auto it = result.cbegin(); it != result.cend(); ++it) cout << *it; cout << endl; return; } //组合里包含第一个字符 result.push_back(*pStr); Combination(pStr + 1, size - 1, result); //组合里不包含第一个字符 result.pop_back(); Combination(pStr + 1, size, result); } void Combination(char* pStr) { if(pStr == NULL) return; for(size_t size = 1; size <= strlen(pStr); ++size) { vector<char> result; Combination(pStr, size, result); } }八皇后问题:
8皇后问题也可以用全排列的方式解决。问题是这样的:在8*8的国际象棋上摆放8个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行、同一列或者同一对角线上。求一共有多少种符合条件的八法。
由于8个皇后任意两个不能处在同一行,那么肯定每个皇后占据一行。于是我们可以定义一个数组ColumnIndex[8],数组中第i个数字表示位于第i行皇后的列号。先把数组ColumnIndex的8个数字分别用0~7初始化,接下来就是对数组ColumnIndex做全排列。因为我们是用不同的数字初始化数组,所以任意连个皇后肯定不同列。只需要判断每一个排列对应的8个皇后是不是在同一对角线上,也就是对于数组的两个下标i和j,是不是i-j==ColumnIndex[i] - ColumnIndex[j]或者j-i=ColumnIndex[i] - ColumnIndex[j]。
#include <iostream> using namespace std; bool IsRightArrange(int* ColumnIndex, int n) { for(int i = 0; i < n; ++i) { for(int j = i+1; j < n; ++j) { if(i - j == ColumnIndex[i] - ColumnIndex[j] || j - i == ColumnIndex[i] - ColumnIndex[j]) return false; } } return true; } void Permutation(int* ColumnIndex, int* pBegin, int& count) { if(pBegin - ColumnIndex == 8) { if(IsRightArrange(ColumnIndex, 8)) ++count; } else { for(int* pSwap = pBegin; pSwap != ColumnIndex + 8; ++pSwap) { int temp = *pBegin; *pBegin = *pSwap; *pSwap = temp; Permutation(ColumnIndex, pBegin + 1, count); temp = *pBegin; *pBegin = *pSwap; *pSwap = temp; } } } int Queen8() { int ColumnIndex[8]; for(int i = 0; i < 8; ++i) ColumnIndex[i] = i; int count = 0; Permutation(ColumnIndex, ColumnIndex, count); return count; } int main() { cout << Queen8() << endl; getchar(); return 0; }