您的位置:首页 > 其它

字符串的排列

2015-08-31 18:44 483 查看
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

[b]输入描述:[/b]
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。


class Solution {
public:
vector<string> Permutation(string str) {
vector<string> res;
int n=str.size();
if(n<1) return res;
string s(str);
StringPermutation(s,0,n-1,res);
return res;
}
private:
void StringPermutation(string &str,int begin,int end,vector<string>& res){
if(begin==end)
res.push_back(str);
for(int i=begin;i<=end;i++){
if(isSame(str,begin,i))
continue;
else{
swap(str[begin],str[i]);
StringPermutation(str,begin+1,end,res);
swap(str[begin],str[i]);
}
}
}
bool isSame(string str,int begin,int end){
for(int i=begin;i<end;i++){
if(str[i]==str[end])
return true;
}
return false;
}
};


来源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)来求解问题。

//函数功能 : 求一个字符串某个区间内字符的全排列
//函数参数 : pStr为字符串,begin和end表示区间
//返回值 :   无
void Permutation_Solution1(char *pStr, int begin, int end)
{
if(begin == end - 1) //只剩一个元素
{
for(int i = 0; i < end; i++) //打印
cout<<pStr[i];
cout<<endl;
}
else
{
for(int k = begin; k < end; k++)
{
swap(pStr[k], pStr[begin]); //交换两个字符
Permutation_Solution1(pStr, begin + 1, end);
swap(pStr[k],pStr[begin]);  //恢复
}
}
}

//函数功能 : 求一个字符串某个区间内字符的全排列
//函数参数 : pStr为字符串,pBegin为开始位置
//返回值 :   无
void Permutation_Solution2(char *pStr, char *pBegin)
{
if(*pBegin == '\0')
{
cout<<pStr<<endl;
}
else
{
char *pCh = pBegin;
while(*pCh != '\0')
{
swap(*pBegin, *pCh);
Permutation_Solution2(pStr, pBegin + 1);
swap(*pBegin, *pCh);
pCh++;
}
}
}
//提供的公共接口
void Permutation(char *pStr)
{
Permutation_Solution1(pStr, 0, strlen(pStr));
//Permutation_Solution2(pStr,pStr);
}


二、去掉重复的全排列的递归实现
由于全排列就是从第一个数字起每个数分别与它后面的数字交换。我们先尝试加个这样的判断——如果一个数与后面的数字相同那么这二个数就不交换了。如122,第一个数与后面交换得212、221。然后122中第二数就不用与第三个数交换了,但对212,它第二个数与第三个数是不相同的,交换之后得到221。与由122中第一个数与第三个数交换所得的221重复了。所以这个方法不行。

换种思维,对122,第一个数1与第二个数2交换得到212,然后考虑第一个数1与第三个数2交换,此时由于第三个数等于第二个数,所以第一个数不再与第三个数交换。再考虑212,它的第二个数与第三个数交换可以得到解决221。此时全排列生成完毕。
这样我们也得到了在全排列中去掉重复的规则——去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。下面给出完整代码:

#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:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入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。

//函数功能 : 从一个字符串中选m个元素
//函数参数 : pStr为字符串, m为选的元素个数, result为选中的
//返回值 :   无
void Combination_m(char *pStr, int m, vector<char> &result)
{
if(pStr == NULL || (*pStr == '\0'&& m != 0))
return;
if(m == 0) //递归终止条件
{
for(unsigned i = 0; i < result.size(); i++)
cout<<result[i];
cout<<endl;
return;
}
//选择这个元素
result.push_back(*pStr);
Combination_m(pStr + 1, m - 1, result);
result.pop_back();
//不选择这个元素
Combination_m(pStr + 1, m, result);
}
//函数功能 : 求一个字符串的组合
//函数参数 : pStr为字符串
//返回值 :   无
void Combination(char *pStr)
{
if(pStr == NULL || *pStr == '\0')
return;
int number = strlen(pStr);
for(int i = 1; i <= number; i++)
{
vector<char> result;
Combination_m(pStr, i, result);
}
}


问题3:打靶问题。一个射击运动员打靶,靶一共有10环,连开10 枪打中90环的可能性有多少?

思路:这道题的思路与字符串的组合很像,用递归解决。一次射击有11种可能,命中1环至10环,或脱靶。

参考代码:

//函数功能 : 求解number次打中sum环的种数
//函数参数 : number为打靶次数,sum为需要命中的环数,result用来保存中间结果,total记录种数
//返回值 :   无
void ShootProblem_Solution1(int number, int sum, vector<int> &result, int *total)
{
if(sum < 0 || number * 10 < sum) //加number * 10 < sum非常重要,它可以减少大量的递归,类似剪枝操作
return;
if(number == 1) //最后一枪
{
if(sum <= 10) //如果剩余环数小于10,只要最后一枪打sum环就可以了
{
for(unsigned i = 0; i < result.size(); i++)
cout<<result[i]<<' ';
cout<<sum<<endl;
(*total)++;
return;
}
else
return;
}
for(unsigned i = 0; i <= 10; i++) //命中0-10环
{
result.push_back(i);
ShootProblem_Solution1(number-1, sum-i, result, total); //针对剩余环数递归求解
result.pop_back();
}
}
//提供的公共接口
void ShootProblem(int number, int sum)
{
int total = 0;
vector<int> result;
ShootProblem_Solution1(number, sum, result, &total);
cout<<"total nums = "<<total<<endl;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: