跳到主要内容

深度优先搜索(dfs)算法之二——枚举&排列

1.1 指数型枚举

从 1 ~ n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。 同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。 对于没有选任何数的方案,输出空行。 本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围

1 ≤ n ≤ 15

输入样例:
3
输出样例:
3
2
23
1
13
12
123

指数型枚举是指一共有n个数,每一个数都有两种状态,也就是选或不选,时间复杂度也就是2^n,指数级的 例如3个数的枚举时,每一个数都有选和不选两种状态,可以根据这个画出递归搜索树:

深度优先搜索DFS指数枚举全排列

指数型枚举,选或不选
#include <iostream>
using namespace std;
int n;
int vis[20];
void dfs(int x){
//表示已经n个数都被判断过了,一种方案已经搜索完成
if(x > n){
for(int i = 1;i <= n;i++){
if(vis[i] == 1)
cout<<i<<" ";
}
cout<<'\n';
return;
}
vis[x] = 2;//表示不选
dfs(x+1);//继续搜下一个
vis[x] = 0;//回溯
vis[x] = 1;//表示选
dfs(x+1);//继续搜下一个
vis[x] = 0;//回溯
}
int main(){
cin>>n;
dfs(1);
return 0;
}

2.2 排列型枚举

排列型枚举是一种生成给定集合所有可能排列的方法,其实在中学阶段我们就学过排列组合的问题,排列是区分顺序的,例如,同样是1 2 3三个数字,1,2, 3和 1,3,2是两种方案。 来看下面的一个例题:

题目描述

按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式

一个整数 n。

输出格式

由 1 ~ n 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 5 个场宽。

输入输出样例

输入 #1

3

输出 #1

    1    2    3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

说明/提示

1 ≤ n ≤ 9。

解题思路就是生成n个数字的全排列方案,在用代码实现的过程中,需要另外再开一个vis数组,表示状态,以此来区分是否被选过。

排列型枚举,全排列
#include <bits/stdc++.h>
using namespace std;
int vis[15];
int a[15];
int n;
void dfs(int x){
//表示n个数字都已经选过了
if(x > n){
for(int i = 1;i <= n;i++){
cout<<setw(5)<<a[i];//题目中要求5个场宽
}
cout<<'\n';
return;
}
for(int i = 1;i <= n;i++){
if(!vis[i]){
vis[i] = 1;//选过标记为1
a[x] = i;//表示该数字被选上了
dfs(x+1);//继续选下一个数字
vis[i] = 0;//回溯重置该数字的状态
a[x] = 0;//,也可以不写,因为数据可以直接覆盖
}
}
}
int main(){
cin>>n;
dfs(1);
return 0;
}

2.3 组合型枚举

组合是从n个不同元素中取出m(m≤n)个元素的所有取法,组合不考虑元素的顺序。也就是 1 2 3 和 1 3 2是同一种方案

题目描述

排列与组合是常用的数学方法,其中组合就是从 n 个元素中抽出 r 个元素(不分顺序且 (r \leq n)),我们可以简单地将 n 个元素理解为自然数 1, 2, ..., n,从中任取 r 个数。

现要求你输出所有组合。

例如 (n=5, r=3),所有组合为:

123, 124, 125, 134, 135, 145, 234, 235, 245, 345。

输入格式

一行两个自然数 (n, r (1 < n < 21, 0 \leq r \leq n))。

输出格式

注意哦! 输出时,每个数字需要 3 个场宽。以 C++ 为例,你可以使用下列代码:

cout << setw(3) << x;

输出占 3 个场宽的数 x。注意你需要头文件 iomanip。

输入输出样例

输入 #1输出 #1
5 31 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5

这次的dfs中采用了两个参数,一个表示枚举了几个数,一个表示从哪个数开始往后选,因为这次是组合型枚举,例如,在选了1 3 2之前,1 2 3肯定也已经选过了,所以不会有1 3 2这种情况出现,从哪个数开始往后选,都是选的比这个数字典序大的数,不存在字典数大的数排在字典数小的之前的情况,所以要记录从哪个数开始往后选

组合型枚举,全组合
#include <bits/stdc++.h>
using namespace std;
int a[25];
int n,r;
void dfs(int x,int start){
//已经选够的情况
if(x > r){
for(int i = 1;i<=r;i++){
cout<<setw(3)<<a[i];
}
cout<<'\n';
return;
}
for(int i = start;i<=n;i++){
a[x] = i;
dfs(x+1,i+1);//选下一个数字,并且下一个数字的字典序要比本次大,也就是从i+1开始往后选
a[x] = 0;
}
}
int main(){
cin>>n>>r;
dfs(1,1);
return 0;
}

扩展练习

  1. P1706 全排列问题
  2. B3623 枚举排列(递归实现排列型枚举)
  3. P1036 [NOIP2002 普及组] 选数
  4. P1088 [NOIP2004 普及组] 火星人

***本章节,主要是dfs在枚举方面的应用,未完待续!

*学习笔记

暂没有学习笔记,快来抢first blood !