全排列
全排列
//这篇题解是我第一篇题解,很久以前写的,就作为我blog的第一篇文章吧
思路
递归。
N个数的全排列数,观察3个数的全排列数:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
可以看到第一个分别是1 2 3,而各自后面所跟着的,恰恰是剩下的数的全排列,以1为例,后面的 2 3 和 3 2是2、3的全排列
又比如4个数全排列:
即先依字典序安排一个数在首位,而剩下的数则在后面进行全排列,这就体现出了递归的思想,由此可见我们可以用递归来处理这个问题
抽象出来就是
Permutation(N){
Head();
Permutation(N-1);
}
代码与说明
/*
之前的代码是
void permutation(char *s) {
if (strlen(s) == 1) {
printf("%c\n", s[0]);
}
else {
int i;
for (i = 0; i < strlen(s); i++) {
printf("%c",s[i]);
char s1[11] = "";
char *p = s1;
for (j = 0; j < strlen(s); j++) {
if (j != i) { *(p++) = s[j]; }
}
permutation(s1);
}
}
}
这样是有问题的,不定义before以存储已排列过的部分,则每次递归,都只打印出当前层次未确定的字符的排列。因为对每一层递归来说,上一层递归只执行一次,也就是说这层递归无论有多少情况,上一层都只打印一次。这就造成了缺项。
以 N=3 为例,以 1 为开头的全排列本该有两个,但是会出现 123 和 32 的结果,在第二个结果中1消失了,这是因为在递归中,处理第一位的操作只出现了一次。因此 N=3 的情况下,输出长度呈现 3 2 3 2 3 2 这样的情况。以上图的树来看,四个*的只输出一次,三个*输出4次,两个*输出12次,对应第一位被确定的情况下,第一位被打印出来的次数、第二位被打印出来的次数以及第三位的。
同理,N=4的情况下,输出长度会呈现 4 3 2 2 4 3 2 2 …… 这样的情况。
*/
//以下正文
#include <stdio.h>
#include <string.h>
#include <malloc.h>
/*定义了两个参数,s指的是待全排列的子串,before则保存是已排列过的部分,如1234,1243中的12即是已排列过的部分*/
/*本质上说全排列是位置变动,而与具体的值无关,因此只需要稍微安排一下字符串(为了按字典序输出),就能放心地使用递归直接对子串全排列*/
void permutation(char *s, char *before) {
if (strlen(s) == 1) {
int i;
for (i = 0; i < strlen(before); i++) {
printf("%c ", before[i]);
}
printf("%c\n", s[0]);
}
else {
int i;
for (i = 0; i < strlen(s); i++) {
int j;
char newBefore[11] = "";
/*这里之所以声明一个新的before是为了给每个下级递归分配独立的before避免冲突,事实上直接使用before会导致before一直增加元素直到数组溢出*/
for (j = 0; j < strlen(before); j++) {
newBefore[j] = before[j];
}
newBefore[strlen(newBefore)] = s[i];
char s1[11] = "";
char *p = s1;
for (j = 0; j < strlen(s); j++) {
if (j != i) { *(p++) = s[j]; }
}
/*对于每一次循环都进行一次递归调用,因为树的每个节点都有相同数量的子树,因此各自递归*/
permutation(s1, newBefore);
}
}
}
int main() {
char s[11] = "";
int N;
scanf("%d", &N);
int i;
for (i = 0; i < N; i++) {
s[i] = (char) (i + '0' + 1);
}
char before[11] = "";
permutation(s, before);
return 0;
}