字符串全排列的两个例子:
1.abc的全排列有:
abc
acb
bac
bca
cba
cab
2.aab的全排列有:
aab
aba
baa
思路:使用回溯法。对于abc,a和a交换仍是abc;a和b交换可得bac;a和c交换可得cba,至此第一个字母的遍历结束。从上述结果中进行第二个字母的遍历:对于abc,b和b交换仍是abc,b和c交换可得acb;对于bac,a和a交换仍是bac,a和c交换可得bca;对于cba,b和b交换仍是cba,b和a交换可得cab。至此我们得到abc的全部6种排列。归纳上述方法,即:对于长度为n的字符串,需要对其第1至第n-1个字符进行遍历。所谓“遍历”是指将该位置的字符与其本身以及其后的所有字符均交换一遍,得出下一轮“遍历”的若干分支。第一轮遍历会获得n个分支,第二轮在此基础上获得n*(n-1)个分支,第三轮为n*(n-1)*(n-2)个分支,……,第n-1轮为n*(n-1)*(n-2)*……*2 = n!个分支,正符合全排列公式
上述分析只针对无重复字符的字符串。对于有重复字符的字符串上述算法不会得出正确结果,以abb为例:第一轮的一个分支是bab,另一个分支是bba。第二轮中bba的一个分支是bab,这与第一轮得到的bab重复,因此出错
我们只需对上述算法做一些改进即可适用于有重复字符的字符串,具体为:在某一轮“遍历”中,若当前准备被交换的字符在该轮已被交换过,则跳过该次交换
代码:
void process(char * s, int k){
if(k == strlen(s)-1) //递归的出口,若遍历至最后一个字符,则无法再交换,打印此分支
printf("%s\n", s);
else{
for(int i = k; i < strlen(s); i++){ //交换包括其本身及之后的所有字符
int exist = 0; //标记当前计划交换的字符之前是否出现过,0为未出现
for(int j = k; j < i; j++) //从k扫描至i-1
if(s[j] == s[i]){
exist = 1;
break;
}
if(!exist){ //若未出现过,则进行交换
swap(&s[k], &s[i]);
process(s, k+1); //交换后进行递归,进行下一轮“遍历”
swap(&s[k], &s[i]); //必须交换回来,为了本轮其他分支使用
}
}
}
}