1. 问题定义
- 输入整数n,按照字典序从小到大输出前n个数得到所有排列。
即若n=3,则有全排列 { 123 , 132 , 213 , 231 , 312 , 321 } \{123,132,213,231,312,321\} { 123,132,213,231,312,321}。 - 或者输入含有n个元素的集合,输出这n个元素的 A n n A_n^n Ann 种排列结果。
2. 递归生成1~n的排列
根据我们上面举的关于字典序排列生成的方式,我们可以用递归的方式来生成排列:
即对于 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n 的排列,我们先生成1开头的排列,再生成2开头的排列,接着是3开头的排列,…,依此类推,最后是n开头的排列。
子问题的定义:
- 生成1开头的排列的时候,我们第一位是1,剩下是 2 , 3 , . . , n 2,3,..,n 2,3,..,n 组成的子排列,所以我们递归的时候主要看首元素和子排列的关系。
- 即我们递归函数需要保留以下参数:
- 已经确定的“前缀序列A”,便于输出;
- 还需要进行排列的元素集合S,便于从中选取下一元素。
- 所以我们有伪代码
void permutation(前缀A,序列S){
if(序列S为空) 输出序列A;
else{
按照从小到大的顺序依次取出S中每个元素u{
permutation(A + u, S - {
u});
}
}
}
实现一:暴力实现
基于上面的伪代码,我们可以先写一个简单的实现:
void permutation(int n, int* A, int index) {
/*
** A表示生成的排列序列,n表示排列的元素个数,index表示当前生成元素的位置
*/
if (index == n) {
// 一个排列生成结束
for (int i = 0; i < n; i++) printf("%d ", A[i]);
printf("\n"); return;
}
// 按照从小到大依次取出1-n生成排列
for (int i = 1; i <= n; i++) {
int ok = 1; // 没有被取过
for (int j = 0; j < index; j++) {
if (i == A[j]) {
ok = 0; break; }
}// for
if (ok) {
A[index] = i;
permutation(n, A, index + 1);
}
}
}
分析:
每次我们要从未选区的元素集合 S S S 中选择元素时,我们都需要先遍历 i 从 1 到 n i从1到n i从1到n 选择我们需要的元素,然后再遍历 j 从 0 到 i n d e x j从0到index j从0到index 判断该元素是否被选择过,导致当n比较大时,越是到后面的选取,我们越是要遍历两边数组,即复杂度接近 O ( n 2 ) O(n^2)