DFS总结
DFS介绍
DFS(Depth First Search)是一种搜索方式,时间复杂度很高,适用小数据,原理是自己调用自己。
DFS实质上是递归,包括四要素:类型,参数列表,边界,调用。
以下面一个为引例:
long long/*类型*/j(int x)/*参数列表*/{
if(x==1) return 1;//边界
return j(x-1)*x;//调用
}
这实现了求
x
x
x 阶乘
x
!
=
1
×
2
×
⋯
×
x
x!=1 \times 2 \times \dots \times x
x!=1×2×⋯×x。
示例:求
3
3
3 阶乘 :
∵
j
(
x
)
=
j
(
x
−
1
)
×
x
,
j
(
1
)
=
1
\because j(x)=j(x-1) \times x,j(1)=1
∵j(x)=j(x−1)×x,j(1)=1
∴
j
(
3
)
=
j
(
3
−
1
)
×
3
=
j
(
2
)
×
3
=
j
(
2
−
1
)
×
2
×
3
=
j
(
1
)
×
2
×
3
=
1
×
2
×
3
=
6
\therefore j(3)=j(3-1) \times 3=j(2) \times 3=j(2-1) \times 2 \times 3=j(1) \times 2 \times 3=1 \times 2 \times 3=6
∴j(3)=j(3−1)×3=j(2)×3=j(2−1)×2×3=j(1)×2×3=1×2×3=6
∴
j
(
3
)
=
6
\therefore j(3)=6
∴j(3)=6
递归有时也很快,如求两个数的最大公约数:
int gcd(int x,int y){
if(y==0) return x;
return gcd(y,x%y);
}
通过辗转相除法很快地求两个数的最大公约数。
枚举方式
DFS用一句话概括就是:“一直往下走,走不通回头,换条路再走,直到无路可走”。
枚举方式有三种:
- 子集枚举
- 排列枚举
- 组合枚举
子集枚举
子集枚举即枚举集合中的每个子集。
每个数有选或不选两种状态,所以时间复杂度为
O
(
2
n
)
O(2^n)
O(2n)。
书架
就是枚举子集,求和,与
b
b
b 比较,得出答案。
代码如下:
int n,vis[33]={},a[33]={},ans=inf,b;//vis为该数选或不选,a为集合
void dfs(int k){
if(k==n+1){
int sum=0;
for(int i=1;i<=n;i++) sum+=vis[i]*a[i];
if(sum>=b) ans=min(ans,sum-b);//更新答案
return;
}for(int i=0;i<=1;i++){
vis[k]=i;
dfs(k+1);
}
}
示例输入
5 16
3
1
3
5
6
示例输出
1
样例解释
子集 { 3 , 3 , 5 , 6 } \{3,3,5,6\} {3,3,5,6} 和为 17 17 17,比 16 16 16 大且比 16 16 16 大 1 1 1。
排列枚举
一组数据的一种排序,叫这组数据中一个排列。
一组数据(
n
n
n 个数)有
A
n
n
=
n
!
A_n^n=n!
Ann=n! 个全排列,所以时间复杂度为
O
(
n
!
)
O(n!)
O(n!)。
递归实现全排列枚举
int n,a[33]={},ans=0,vis[33]={};//a表示全排列,vis表示有没有选
void dfs(int k)/*k表示一个全排列的一个数据*/{
if(k==n+1){
for(int i=1;i<=n;i++) cout<<a[i]<<' ';
cout<<'\n';
return;
}for(int i=1;i<=n;i++){
if(vis[i]==0){
vis[i]=1;//标记
a[k]=i;//设置
dfs(k+1);
vis[i]=0;//回溯
a[k]=0;
}
}
}
示例输入
3
示例输出
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
c++有一个内置的求上一个全排列的函数prev_permutation
,和求下一个全排列的函数next_permutation
。
这可以秒过本题,代码不再堆了。
洛谷P1008三连击
链接
全排列枚举
{
1
,
2
,
…
,
9
}
\{1,2,\dots,9\}
{1,2,…,9},然后每三位一个数,并判断。
设排到
{
1
,
9
,
2
,
3
,
8
,
4
,
5
,
7
,
6
}
\{1,9,2,3,8,4,5,7,6\}
{1,9,2,3,8,4,5,7,6},则三数为
192
,
384
,
576
192,384,576
192,384,576,因为
192
:
384
:
576
=
1
:
2
:
3
192:384:576=1:2:3
192:384:576=1:2:3,所以这是一组合法序列,输出这三个数。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int a[10]={0,1,2,3,4,5,6,7,8,9};
int main(){
do{
int x=a[1]*100+a[2]*10+a[3];//第一个数
int y=a[4]*100+a[5]*10+a[6];//第二个数
int z=a[7]*100+a[8]*10+a[9];//第三个数
if(x==y/2&&x==z/3) cout<<x<<' '<<y<<' '<<z<<endl;//判断
}while(next_permutation(a+1,a+1+9));//全排列函数
return 0;
}
这个代码必定输出:
192 384 576
219 438 657
273 546 819
327 654 981
组合枚举
就是在 n n n 个数中挑选 m m m 个数,时间复杂度为 O ( C m n ) O(C_m^n) O(Cmn)。
组合型枚举
从
1
1
1 到
n
n
n 这
n
n
n 个整数中随机选出
m
m
m 个,首先,同一行内的数升序排列,相邻两个数用一个空格隔开。其次,按字典序输出。
代码如下:
int n,m,vis[33]={},num[33]={};
void dfs(int k,int mini){
if(k==m+1){
for(int i=1;i<=m;i++){
cout<<num[i]<<' ';
}cout<<'\n';return;
}for(int i=mini;i<=n;i++)/*因为按照升序排列,所以新设置一个参数mini*/{
if(vis[i]==0){
vis[i]=1;//标记
num[k]=i;//设置
dfs(k+1,i);//mini设为i,所以一定是升序排列
vis[i]=0;//回溯
}
}
}
示例输入
5 3
示例输出
1 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
3 4 5