全排列
递归
首先来说递归方法是比较容易想到的。
比如 “abc”
首先作为字符串的第一位可以 是 ‘a’,‘b’,‘c’。剩下的字符串任然进行全排列。
第一个字符 | 剩下的字符 | |
---|---|---|
1 | a | bc的全排列 |
2 | b | ac的全排列 |
3 | c | ab的全排列 |
#include <stdio.h>
#include <string.h>
void swap(char * ch1, char * ch2){
char t = *ch1;
*ch1 = *ch2;
*ch2 = t;
}
//递归全排列
void CalcAllPermutation(char * str,int begin, int end){
if(end <= 0){
return;
}
if(begin == end){
printf("%s\n",str);
}else{
for(int i = begin;i <= end;i++){
swap(str+begin,str+i);
CalcAllPermutation(str,begin+1,end);
swap(str+begin,str+i);
}
}
}
int main(int argc, char * argv[]){
char str[] = "abc";
printf("递归全排列:\n");
CalcAllPermutation(str,0,strlen(str)-1);
return 0;
}
非递归
非递归方式,简而言之就是:
比如 “123”
如果看成数字最小的就是123,最大的就是321。字符串也有大小,称为字典序。这样我们就可以设计一个函数,获取当前字符串的下一个比它大的字符串。还是以 “123”为例子,
f
(
"
123
"
)
=
“
213
”
f("123") = “213”
f("123")=“213”,
f
(
"
213
"
)
=
“
231
”
f("213") = “231”
f("213")=“231”,
f
(
"
231
"
)
=
"
312
"
f("231") = "312"
f("231")="312"…
f
(
"
321
"
)
=
“
”
f("321") = “”
f("321")=“” 结束
次数 | 值 |
---|---|
0 | 123 |
1 | 132 |
2 | 213 |
3 | 231 |
4 | 312 |
5 | 321 |
那么这个函数如何设计呢?
1
找到最右边的一个需要变大的元素。
当前字符串 | 需要变大的元素 | 备注 |
---|---|---|
123 | 2 | 2->3 |
132 | 1 | 1->2 |
213 | 1 | 1->2 |
231 | 2 | 2->3 |
其实我们就是要从又往左扫描字符串str 找到第一个 s t r [ i ] < s t r [ i + 1 ] str[i] < str[i+1] str[i]<str[i+1]。如果找不到就说明已经是最大的字典序。也就是全排列结束。
2
接下来就是要找i右边的字符串中的比str[i]大的所有元素中最小的一个。str[j],然后和str[i]互换位置,并且把i右边的字符串变成最小字典序排列。得一点一点变大是吧,不能漏。然后我用数字来解释, 199 + 1 = 200 199+1=200 199+1=200,不看百位99正好就是最大的,00正好就是最小的。所以我们需要,从右往左找到第一个比第一步找到的元素大的元素。和str[i]互换并且把i右边的字符串倒置。应为肯定是要先到最大字典序才会需要“进位”。而且是把字符串中最小的一个元素换成了一个更小的元素。所以当前任然是最大字典序,倒置就是最小字典序。
当前字符串 | 需要变大的元素 | 第二步找到的元素 | 交换 | 倒置 |
---|---|---|---|---|
123 | 2 | 3 | 132 | 132 |
132 | 1 | 2 | 231 | 213 |
213 | 1 | 3 | 231 | 231 |
231 | 2 | 3 | 321 | 312 |
#include <stdio.h>
#include <string.h>
void swap(char * ch1, char * ch2){
char t = *ch1;
*ch1 = *ch2;
*ch2 = t;
}
//翻转字符串
void reverse(char * buffer,int len)
{
char t;
for(int i = 0;i<len/2;i++)
{
t = *(buffer+i);
*(buffer+i) = *(buffer+len-i-1);
*(buffer+len-i-1) = t;
}
}
//非递归全排列 要求初始字符串字典序最小排列
int _CalcAllPermutation(char * str){
int i;
int len = strlen(str);
//找最后一个 可以升序的位置
for(i = len - 2; (i>=0) && str[i+1] <= str[i];i--);
//全排列完成
if(i < 0){
return 0;
}
int j;
//找到i右边最后一个比 str[i]大的元素 应为此时 i 右边一定是字典序最大排列
for(j = len-1; (j > i) && str[j] <= str[i]; j--);
swap(str+i,str+j);
reverse(str+i+1,len-i-1);
printf("%s\n",str);
return 1;
}
void CalcAllPermutation(char * str){
printf("%s\n",str);
while( _CalcAllPermutation(str) );
}
int main(int argc, char * argv[]){
char str[] = "abc";
printf("非递归全排列:\n");
CalcAllPermutation(str2);
return 0;
}