搜索入门练习题2 全排列 题解
题目出处:课程=>搜索1=>题目A
题目描述
给定一个正整数 \(n\) ,按照递增顺序打印数字 \(1\) 到 \(n\) 的所有排列。
输入格式
一个整数 \(n(1 \le n \le 7)\) 。
输出格式
按照递增顺序输出 \(n\) 个数的所有排列,每行代表一组排列, \(n\) 个数两两之间有一个空格分隔。
样例输入
3
样例输出
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
问题分析
这是一道搜索的题目。
我们知道搜索就是状态到状态之间的转换,其本质是使用递归的方式进行了枚举(注:这句话是非官方解释,不过很便于理解,所以大家先期就这么理解就好)。
这道题目可以用枚举做,但是用枚举做会编写大量的重复代码,所以我们这边使用深度优先搜索来解决。
我们可以开一个 ans[]
数组, ans[id]
用于表示我的当前组合的第 i
个数,然后我开一个函数 void f(int id)
用于表示我当前正准备在组合的第 id
个位置放一个数,然后我只需要从 1 到 n 去遍历一个数 i ,看看 i 能不能放在第 id 个位置(即能否将 ans[id]
设为 i
)。
在第 id
个位置能放 i
,当且仅当:ans[1]
到 ans[id-1]
中的元素都不为 i
,即 i
还没有放过。
这样,当我们的 f(id)
遍历到 id>n
时,就证明找到了一种排列,输出即可。
实现代码如下:
#include<bits/stdc++.h>
using namespace std;
int n, ans[8]; // ans[i]表示当前排列的第i个数是啥
void f(int id) { // 用于在ans[]数组的第id个位置放数
if (id > n) { // 边界条件,说明n个数的一个排列找到了
for (int i = 1; i <= n; i ++)
cout << (i > 1 ? " " : "") << ans[i];
cout << endl;
return; // 输出后返回,不需要继续进行判断了
}
for (int i = 1; i <= n; i ++) { // 尝试在ans[id]放i
bool flag = true;
for (int j = 1; j < id; j ++)
if (ans[j] == i) { // 说明ans[]数据的第j个位置已经放过i了
flag = false;
break;
}
if (flag) { // flag为true说明i可以放
ans[id] = i;
f(id+1); // 递归地放下一个位置
}
}
}
int main() {
cin >> n;
f(1);
return 0;
}
补充知识
这里我会在讲解另外一个实现全排列的方案,但是这种方法并不是使用搜索来实现的,而是每次将当前的这个排列转换成它的下一个排列。比如:1 2 3 4
转换一次会变成 1 2 4 3
,再转换一次会变成 1 3 2 4
,如是循环……
大家可以手动来实现这个程序的编写,但是我们这里先使用 algorithm
库提供给我们的现成的函数——next_permutation
。
比如,给我们一个数组 a[5] = {1, 2, 3, 4, 5}
,我们只需要执行一遍 next_permutation(a, a+5)
,这个数组 a[]
当中的值就会变成它的下一个全排列 {1, 2, 3, 5, 4}
。
并且,next_permutation
的返回值是 bool
类型的,如果当前的排列有下一个排列,调用它会返回 true
,同时将当前排列转成下一个排列,如果当前排列已经是全排列里面的最后一个排列了(例如当 a[5]={5, 4, 3, 2, 1}
就已经是全排列里面的最后一个排列了),它会返回 false
。
使用 next_permutation
函数解决全排列问题的代码如下:
#include<bits/stdc++.h>
using namespace std;
int n, a[] = { 1, 2, 3, 4, 5, 6, 7 };
void output() {
for (int i = 0; i < n; i ++)
cout << (i ? " " : "") << a[i];
cout << endl;
}
int main() {
cin >> n;
do output(); while (next_permutation(a, a+n));
return 0;
}
思考一下:为什么我的代码里面使用了 do...while
循环,而不是 while
循环。