描述
给定一个正整数n,输出1~n的全排列
分析1
首先,根据递归的思想,我们可以把求1~n的全排列这个大问题分解为先固定1,求剩下的n-1个数的全排列,在固定2,固定3……依次类推。而对于剩下的这n-1个数,同样采用这种方法,取里面的第一个数,放到当前排列的第一个位置,再取第二个数放到第一个位置……
即:对于1,2,3,4……n,这n个数,分别求
1,2,3,……,n
2,3,……,n
3,……,n
……
n
4,……,n
……
n
……
n,……,n-1
3,2,……,n
……
……
n,1,……,n-1
2,1,3,……,n
3,1,2,……,n
……
n,1,2,……,n-1
对于分别以1到n开头,可以通过交换的思想来实现,在递归前先将待排元素与当前排列的第一个元素进行交换,在递归结束时,还应交换回来,如1,2,3,在处理2开头的排列时,先将2和1交换,将2固定到排列的第一个位置。得到2,1,3,然后处理1和3。待处理完毕输出以2开头的全部排列后,递归返回到2,1,3这个状态,在进行一次交换,将2和1交换回来,得到1,2,3这个原始状态,在进入下一轮,继续将3和1交换,得到3,1,2,然后进行同样的处理。
代码实现:
#include <iostream>
using namespace std;
void swap(int &a, int &b) {//交换
int t = a;
a = b;
b = t;
}
void perm(int num, int n, int a[]) {
if (num == n) {//已处理完n个元素,可输出一组排列
for (int i = 1; i <= n; ++i) {
cout << a[i] << " ";
}
cout << endl;
return;
}
for (int j = num; j <= n; ++j) {
swap(a[j], a[num]);
perm(num + 1, n, a);
swap(a[j], a[num]);
}
}
int main() {
int n;
int a[11];
cin >> n;
for (int i = 1; i <= n; i++) {
a[i] = i;
}
perm(1, n, a);
return 0;
}
输出如下:
时间复杂度分析
其递归方程为:T(n)=nT(n-1)+n,因而其时间复杂度要略高于n!,可近似为n!。
分析2
从上面的输出可以看到,其并未遵从字典序。故可以采用回溯的策略,构建一棵解空间树 ,进行深度优先搜索,到达叶子结点时即可构成一组排列(从根到叶子的一组完整路径)。每向下扩展一层,试图填入一个数,如果该数已经被其祖先使用过,则不可再使用,应判断下一个数,这样到第n+1层时,表明1到n个数已经全部填入,便可结束本次递归过程,并输出当前排列。并且每当返回上一层后,应将该层所填入的数重新标记为未被使用过。
如图:
代码实现
#include <iostream>
using namespace std;
bool flag[10] = {false};//标记数组
int permutation[10];//保存当前排列
void dfs(int level, int n) {
if (level == n + 1) {//如果到达了第n+1层,则输出
for (int j = 1; j <= n; ++j) {
cout << permutation[j];
}
cout << endl;
return;
}
for (int j = 1; j <= n; ++j) {//依次将1到n这几个数填入
if (flag[j] == false) {//判断当前j是否被用过
permutation[level] = j;
flag[j] = true;
dfs(level + 1, n);//扩展下一层
flag[j] = false;//递归返回上一层,将j重新标记未被使用
}
}
}
int main() {
int n;
cin >> n;
dfs(1, n);
return 0;
}
时间复杂度分析
第一层有n种选择,第二层有n-1种,第三层有n-2种……故时间复杂度为n!。