今天学习了一下何海涛博客中的第28题,字符串的排列问题,实际上指的是字符串的全排列问题(排列和全排列还是有区别的吧)。思考并研究了这题之后就考虑了一下不同条件下其他类似的题的解法的编写,两部分来自于何海涛,其他来自于网络,此处做搬运和收集工作。分别从四个方面考虑:一、字符串的全排列
(1)若不考虑字符串中有重复字符(即假设字符串中无重复字符)
(2)若考虑字符串中有重复字符(即假设字符串中有重复字符)
二、字符串的组合
(1)若不考虑字符串中有重复字符(即假设字符串中无重复字符)
(2)若考虑字符串中有重复字符(即假设字符串中有重复字符)
一、字符串的全排列
(1)若不考虑字符串中有重复字符(即假设字符串中无重复字符)
此时“abc”的全排列为:abc、acb、bac、bca、cab和cba。
我们以三个字符abc为例来分析一下求字符串排列的过程。首先我们固定第一个字符a,求后面两个字符bc的排列。当两个字符bc的排列求好之后,我们把第一个字符a和后面的b交换,得到bac,接着我们固定第一个字符b,求后面两个字符ac的排列。现在是把c放到第一位置的时候了。记住前面我们已经把原先的第一个字符a和后面的b做了交换,为了保证这次c仍然是和原先处在第一位置的a交换,我们在拿c和第一个字符交换之前,先要把b和a交换回来。在交换b和a之后,再拿c和处在第一位置的a进行交换,得到cba。我们再次固定第一个字符c,求后面两个字符b、a的排列。既然我们已经知道怎么求三个字符的排列,那么固定第一个字符之后求后面两个字符的排列,就是典型的递归思路了。
void Permutation(char* pSrc, char* pBegin)
{
if (!pSrc || !pBegin)
{
return;
}
if (*pBegin == '\0')
{
printf("%s\n", pSrc);
}
else
{
for (char* pCh=pBegin; *pCh!='\0'; ++pCh)
{
char temp = *pBegin;
*pBegin = *pCh;
*pCh = temp;
Permutation(pSrc, pBegin+1);
temp = *pBegin;
*pBegin = *pCh;
*pCh = temp;
}
}
}
int main()
{
char pSrc[] = "abc";
Permutation(pSrc, pSrc);
return 0;
}
运行结果是:

(2)若考虑字符串中有重复字符(即假设字符串中有重复字符)
上述思路非常好,但是若存在重复字符,则就不正确了,比如对于上述程序,输入“aabc”,则输出:

就会出现重复的结果。我本来思考着如果在与*pBegin交换时加一个条件语句判断是否相等,若与*pBegin相等就不交换,若不相等才交换,用“aabc”测试时结果确实正确了。但是实际上是错误的,比如用“abbc”测试时就不正确了。
我也是看到了一个网友的方法才发现我上述考虑的方法是错误的,他的方法是判断当前*pCh的字符(即准备与*pBegin交换的字符)在前面的子字符串中是否出现过了,若出现了,就不交换,若没出现就交换。代码如下:
void Permutation(char* pSrc, char* pBegin)
{
if (!pSrc || !pBegin)
{
return;
}
if (*pBegin == '\0')
{
printf("%s\n", pSrc);
}
else
{
for (char* pCh=pBegin; *pCh!='\0'; ++pCh)
{
if(strchr(pBegin, *pCh) == pCh)
{
char temp = *pBegin;
*pBegin = *pCh;
*pCh = temp;
Permutation(pSrc, pBegin+1);
temp = *pBegin;
*pBegin = *pCh;
*pCh = temp;
}
}
}
}
int main()
{
char pSrc[] = "abbc";
Permutation(pSrc, pSrc);
return 0;
}
运行结果是:

二、字符串的组合
(1)若不考虑字符串中有重复字符(即假设字符串中无重复字符)
输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入" abc",它的组合有a、b、c、ab、ac、bc、abc。
本题也可以用递归的思路来求字符串的组合。
假设我们想在长度为n的字符串中求m个字符的组合。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;而是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。下面是这种思路的参考代码:
void Combination(char* str, int number, vector<char>& result)
{
if (str==NULL)
{
return;
}
if (number==0)
{
for (vector<char>::iterator ite = result.begin(); ite!=result.end(); ++ite)
{
cout<<*ite;
}
cout<<endl;
return;
}
if (*str == '\0')
{
return;
}
else
{
Combination(str+1, number, result);
result.push_back(*str);
Combination(str+1, number-1, result);
result.pop_back();
}
}
int main()
{
char pSrc[] = "abc";
vector<char> result;
for (int i=1; i<=strlen(pSrc); ++i)
{
Combination(pSrc, i, result);
}
return 0;
}
运行结果是:

也可以去掉上述中的循环:
void Combination(char* str, int number, vector<char>& result)
{
if(*str == '\0')
{
for(vector<char>::iterator ite = result.begin();
ite != result.end();
++ite)
{
cout<<*ite;
}
cout<<endl;
return;
}
result.push_back(*str);
Combination(str + 1, number - 1, result);
result.pop_back();
Combination(str + 1, number-1, result);
}
int main()
{
char pSrc[] = "abc";
vector<char> result;
Combination(pSrc, strlen(pSrc), result);
return 0;
}
另外,看到一个网友的思路: 用一个数组,模拟2进制加法器,某一个为1,则取对应的字符,若为0则不取,就能够实现字符组合。这个思路也非常好~ 不过是在字符长度不超过32的情况下。
(2)若考虑字符串中有重复字符(即假设字符串中有重复字符)
但是上述代码在字符串中有重复字符时就出问题了,如输入”abbc“,则输出为:

就出现了重复的结果。
我想可以先去掉字符串中所有重复的字符,比如由“abbcc”转换成“abc”,再用上述算法来做。不知道其他的更高效的算法,待续......