题目:输入一个字符串,打印出该字符串中字符放入所有排列。例如输入字符串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;
}