题目描述
输入一个字符串,打印出该字符串中字符的所有排列。
例如输入字符串abc,则输出由字符a、b、c 所能排列出来的所有字符串
abc、acb、bac、bca、cab 和 cba。
解法一:
递归实现,从集合中选中一个元素作为固定的第一个元素,对后面的的元素进行全排序,选出第二个元素作为固定的第一个元素,对后面的进行全排序,依次类推,直到所有元素都排过固定的第一个元素。
void CallAllPermutation(char *array,int front, int end)
{
if(end<=1)
return ;
if(front==end)//只剩一个元素,输出该排序
{
for(int i=0;i<=end;i++)
cout<<array[i];
cout<<endl;
}
else
{
for(int j=front;j<=end;j++)
{
swap(array[front],array[j]);//将第j个元素作为固定第一个元素
CallAllPermutation(array,front+1,end);
swap(array[front],array[j]);//恢复元排序序列
}
}
}
解法二:字典序排序
字典序排序需要原字符串是正序排序,如“abc”,求它的下一个字典序,为“acb”,如果用数字表示的话21543的下一个排序顺序是23145,具体算法的思想是STL中的next_permutation算法思想,而最后一个排列是54321,也是最大的。
具体做法如下:
对于21543的下一个排列,先从右到左,找到第一对升序对<1,5>,这时从右向左扫描,找到第一个大于<1>的数,为<3>,然后交换<1,3>,变成23541 ,最后再将<3>后的数字进行翻转,得到23145,刚好是21543的下一个数。
bool CalcNextPermutation(char *array,int n)
{
int i;
//找到排列中最后(从右边开始)一个升序的首位位置i,x=ai
for(i=n-2;(i>=0)&&(array[i+1]<=array[i]);i--)
{
;
}
if(i<0)//所有排列都找到
return false;
int k ;
//找到排列中第i位右边最后一个比ai大的位置j(从右边数起),y=aj
for(k=n-1;(k>i)&&(array[k]<=array[i]);k--)
{
;
}
//交换ai,aj
swap(array[i],array[k]);
//把第i+1位到最后的部分进行翻转
reverse(array+i+1,array+n-1);
return true;
}
void CallALLPermutation02(char* array,int n)
{
if(array == NULL || n<0)
return ;
do{
cout<<array<<endl;
}while(CalcNextPermutation(array,n));
}
对于需要去掉重复的全排列
对于122,第一个数1与第二个数交换得到212,然后考虑第一个数1与第三个数2交换,得221,由于第三个数等于第二个数,所以第一个数不再与第三个数交换。对于212,其第二个数与第三个数交换可以得到221。于是可以这样考虑——去重的全排列就是从第一个数其每个数分别于它后面非重复的数字交换。
bool isSwap(char *array,int front,int end)
{
for(int i=front;i<end;i++)
{
if(array[i]==array[end])
return false;
}
return true;
}
void noRepeatPermutation(char *array,int front,int end)
{
if(array==NULL)
return ;
if(front==end+1)
{
for(int i=0;i<=end;i++)
cout<<array[i];
cout<<endl;
}
else
{
for(int i=front;i<=end;i++)
{
if(isSwap(array,front,i))
{
swap(array[front],array[i]);
noRepeatPermutation(array,front+1,end);
swap(array[front],array[i]);
}
}
}
}
解法总结
由于全排列总共有n!种排列情况,所以不论解法一中的递归方法,还是上述解法二的字典序排列方法,这两种方法的时间复杂度都为O(n!)。
全排列问题,总结就是:
1、全排列就是从第一个数字起每个数分别与它后面的数字交换。(递归)
2、去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换。
3、全排列的非递归就是由后向前找替换数和替换点,然后由后向前找第一个比替换数大的数与替换数交换,最后颠倒替换点后的所有数据。(next_permutation)
类似问题
1、已知字符串里的字符是互不相同的,现在任意组合,比如ab,则输出aa,ab,ba,bb,编程按照字典序输出所有的组合。
分析:非简单的全排列问题(跟全排列的形式不同,abc全排列的话,只有6个不同的输出)。 本题可用递归的思想,设置一个变量表示已输出的个数,然后当个数达到字符串长度时,就输出。
void perm(char *result,char *str,int size,int resPos)
{
if(size==resPos)
cout<<result<<endl;
else
{
for(int i=0;i<size;i++)
{
result[resPos]=str[i];
perm(result,str,size,resPos+1);
}
}
}
2、如果不是求字符的所有排列,而是求字符的所有组合,应该怎么办呢?当输入的字符串中含有相同的字符串时,相同的字符交换位置是不同的排列,但是同一个组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。
3、写一个程序,打印出以下的序列。
(a),(b),(c),(d),(e)........(z)
(a,b),(a,c),(a,d),(a,e)......(a,z),(b,c),(b,d).....(b,z),(c,d).....(y,z)
(a,b,c),(a,b,d)....(a,b,z),(a,c,d)....(x,y,z)
....
(a,b,c,d,.....x,y,z)