算法:DFS

3 阅读8分钟

深度搜索

DFS的空间复杂度为 O(h) h表示深入的层数,相较于BFS,其空间复杂度较好,但是时间复杂度较差

DFS的三种类型

分别为指数型,全排列,组合型

指数型搜索

例题:从1-n中随机选取任意多个数,输出所有可能的选择方案

输入格式:输入一个整数 n

输出格式:输出每一种方案,对于没有任何数的方案直接输出空行

数据范围:1<=n<=15

输入样例:3

输出样例:

(空行)

3

2

2 3

1

1 3

1 2

1 2 3

代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
ll st[15];//表示这个位置的数的状态,0表示未知,1表示已选,2表示不选 
void dfs(ll x)
{
	if(x>n)
	{
		for(int i=1;i<=n;i++)
		{
			if(st[i]==1)
			{
				cout<<i<<" ";
			}
		}
		cout<<"\n";
		return ;
	}
	//不选 
	st[x]=2;
	dfs(x+1);
	st[x]=0;
	
	//选 
	st[x]=1;
	dfs(x+1);
	st[x]=0;
}
int main()
{
	cin>>n;
	dfs(1);
	return 0;
}

全排列

例题:全排列问题

AC代码(1)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 1e5+5;
ll n;
ll flag[10];//标记当前数是否被搜索过
ll a[10];//当前位置放的数 
void dfs(ll u)
{
	if(u==n+1)
		{
			for(int i=1;i<=n;i++)
			{
				printf("%5lld",a[i]);
			}
			printf("\n");
		}
	for(int i=1;i<=n;i++)
	{
		
		if(!flag[i])
		{
			a[u]=i;
			flag[i]=1;
			dfs(u+1);
			flag[i]=0;
		}
	}
}
int main()
{
	cin>>n;
	dfs(1);
	return 0;
}

AC代码(2)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
bool a[15];
ll op[15];
void dfs(ll x)
{
	if(x>n)
	{
		for(int i=1;i<=n;i++)
		{
			printf("%4d",op[i]);
		}
		cout<<endl;
		return ;
	}
	for(int i=1;i<=n;i++)
	{
		if(!a[i])
		{
			a[i]=1;
			op[x]=i;
			dfs(x+1);
			a[i]=0;//回溯 
			op[x]=0;//回溯 ,且此处可以省略,因为值会被后面直接替换。 
		}
	}
}
int main()
{
	cin >> n;
	dfs(1);
	return 0;
}

组合型

例题:组合的输出

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll n,r;
ll a[25];
void dfs(ll x,ll st)
{
	if(x>r)
	{
		for(int i=1;i<=r;i++)
		{
			cout<<setw(3)<<a[i];
		}
		cout<<"\n";
		return ;
	}
	for(int i=st;i<=n;i++)
	{
		a[x]=i;
		dfs(x+1,i+1);
		a[x]=0;
	}
}
int main()
{
	cin>>n>>r;
	dfs(1,1);
	return 0;
}
关于个人对以上三种类型的DFS的理解:
指数搜索型是在一堆数中可以取任意数,可以理解为,一个位置,我可以选任何一个数字放在这个位置上。
全排列也是在一堆数中选数,但是数字之间不能有重复。也就是说,一个位置我取了3这个数,别的位置就不能取3 这个数了,而指数型则可以取。
组合型与全排列比较相似,全排列是选中所有,而组合是所有中取部分。
举个例子:一共有五个数,三个位置,这三个位置只能放五个中的三个,高中学过。
以上三者在用代码实现的过程中,需要根据条件对不满足条件的部分进行剪枝,以此来减少计算。
DFS算法在使用之前,建议先判断其属于哪种搜索类型,然和再根据搜索类型画出搜索的全流程图,以方便你对DFS的过程的理解。

DFS模板

int dfs(int t)
{
    if(满足输出条件)
    {
        输出解;
    }
    else
    {
        for(int i=1;i<=尝试方法数;i++)
            if(满足进一步搜索条件)
            {
                为进一步搜索所需要的状态打上标记;
                dfs(t+1);
                恢复到打标记前的状态;//也就是说的{回溯一步}
            }
    }
}

例题:迷宫

转存失败,建议直接上传图片文件

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,t;
ll sx,sy,fx,fy;
ll x,y;
ll dx[4]={0,0,1,-1};
ll dy[4]={1,-1,0,0};
bool flag[10][10];
ll a[10][10];
ll ans;
void dfs(ll tx,ll ty)
{
	if(tx == fx && ty == fy)
	{
		ans++;
		return ;
	}
	else
	{
		for(int i=0;i<4;i++)
		{
			if(flag[tx+dx[i]][ty+dy[i]]==0 && a[tx+dx[i]][ty+dy[i]]==1)
			{
				flag[tx][ty]=1;
				dfs(tx+dx[i],ty+dy[i]);
				flag[tx][ty]=0;
			}
			
		}
	}
}
int main()
{
	cin>>n>>m>>t;
	cin>>sx>>sy>>fx>>fy;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			a[i][j]=1;
		}
	while(t--)
	{
		cin>>x>>y;
		a[x][y]=0;
	}
	dfs(sx,sy);
	cout<<ans;
	return 0;
}

在DFS的使用中,往往会出现计算重复的情况,这种情况一般可以先预处理,将已经计算好的答案存到一个数组中来解决,可大幅降低时间复杂度,从而做到处理一些数据过大的情况

接下来请看实战运用

eg:

点击进入原题! 转存失败,建议直接上传图片文件 转存失败,建议直接上传图片文件

优化前的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN =1e3;
ll n;
ll a[MAXN];
ll b[MAXN]={6,2,5,5,4,5,6,3,7,6};
ll ans;
ll col(ll x)
{
	if(b[x]) return b[x];
	else
	{
		ll sum=0;
		while(x)
		{
			sum+=b[x%10];
			x/=10;
		}
		return sum;
	}
}
void dfs(ll x,ll cnt)//x表示第几个位置的数,一共有三个位置 , cnt表示已使用的火柴数 
{
	if(cnt>n) return ;	
	if(x>3)
	{
//		cout<<a[x]<<" ";
		if(a[1]+a[2]==a[3]&&cnt==n)
		{
			ans++;
		}
		return ;
	}
	for(int i=0;i<=1000;i++)
	{
			a[x]=i;
			dfs(x+1,cnt+col(i));
			a[x]=0;
	}	
}
int main()
{
	cin>>n;
	n-=4;
	dfs(1,0);
	cout<<ans;
	return 0;
}
通过所耗时间如下图:

转存失败,建议直接上传图片文件

优化后的代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN =1e3;
ll n;
ll a[MAXN];
ll b[MAXN]={6,2,5,5,4,5,6,3,7,6};
ll ans;
//ll col(ll x)
//{
//	if(b[x]) return b[x];
//	else
//	{
//		ll sum=0;
//		while(x)
//		{
//			sum+=b[x%10];
//			x/=10;
//		}
//		return sum;
//	}
//}
void dfs(ll x,ll cnt)//x表示第几个位置的数,一共有三个位置 , cnt表示已使用的火柴数 
{
	if(cnt>n) return ;	
	if(x>3)
	{
//		cout<<a[x]<<" ";
		if(a[1]+a[2]==a[3]&&cnt==n)
		{
			ans++;
		}
		return ;
	}
	for(int i=0;i<=1000;i++)
	{
			a[x]=i;
//			dfs(x+1,cnt+col(i));
			dfs(x+1,cnt+b[i]);
			a[x]=0;
	}	
}
int main()
{
	cin>>n;
	n-=4;
	for(int i=10;i<=1000;i++)
	{
		b[i]=b[i%10]+b[i/10];//这步属于优化部分,避免重复计算 
	}
	dfs(1,0);
	cout<<ans;
	return 0;
}
通过所需时间如下图:

转存失败,建议直接上传图片文件

指数型搜索实战例题:

P2036

地图类问题实战例题:

P1683

连通器问题实战例题:(个人理解:先遍历,找到后再搜)

P1596

DFS例题:

跳台阶

acwing 821 跳台阶题解dfs版
//DFS普通版
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll n;
ll ans;
ll dfs(ll x)//x表示目前处于第几个台阶
{
    if(x==1) return 1;
    if(x==2) return 2;
    else return dfs(x-1)+dfs(x-2);
}
int main()
{
    cin>>n;
    ans=dfs(n);
    cout<<ans;
    return 0;
}
//DFS记忆化搜索版
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll n;
ll ans;
ll mem[30];
ll dfs(ll x)//x表示目前处于第几个台阶
{
	if(mem[x]) return mem[x];
	ll sum=0;
    if(x==1) sum = 1;
    else if(x==2) sum =  2;
    else sum = dfs(x-1)+dfs(x-2);
    
    mem[x]=sum;
    return sum;
}
int main()
{
    cin>>n;
    ans=dfs(n);
    cout<<ans;
    return 0;
}
P1683 DFS AC题解

之前用大家都用BFS写过这道题,现在可以换DFS试试

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll w,h;
char s[25][25];//图 
ll sx,sy;//记录初始横纵坐标 
ll dx[4]={1,-1,0,0};//位移向量 
ll dy[4]={0,0,1,-1};
ll ans;//记录格子数 
bool st[25][25];//记录格子的状态,0为尚未走过,1为走过 
void dfs(ll x,ll y)
{
	for(int i=0;i<4;i++)
	{
		ll tx=x+dx[i];
		ll ty=y+dy[i];
		if(tx<0||tx>h||ty<0||ty>w) continue; 
		if(s[x+dx[i]][y+dy[i]]!='.') continue;
		if(st[tx][ty]) continue;
		st[tx][ty]=1;
		ans++;
		dfs(tx,ty);
	}
}	
int main()
{
	cin>>w>>h;//此处题目有坑 
	for(int i=0;i<h;i++)
	{
		cin>>s[i];
	}
	for(int i=0;i<h;i++)
		for(int j=0;j<w;j++)
		{
			if(s[i][j]=='@')
			{
				sx=i;
				sy=j;
				st[sx][sy]=1;
				dfs(sx,sy);
			}
		}
	ans++;//第一次也要算在内 
	cout<<ans;
	return 0;
}

dfs记忆化搜索经典例题:01背包问题 题解:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N = 1010;
ll n,m;
ll v[N],w[N];
ll res;
ll mem[N][N];
// x表示从第x物品开始,spV表示当前背包剩余容量,sumW表示当前背包装的所有物品的总价值 
ll dfs(ll x,ll spV)
{
	if(mem[x][spV]) return mem[x][spV];
	ll sum=0;
	if(x>n) sum = 0;
//	if(sumW > res) res = sumW; 
	else if(spV < v[x])
	{
		// 因为体积不够,只能不选 
		sum = dfs(x+1,spV);
	}
	else if(spV >= v[x])//当剩余容量大于当前物品的体积 
	{
		//就有 选 和 不选 两种子问题 
		sum = max(dfs(x+1,spV),dfs(x+1,spV - v[x])+ w[x]);
	}
	mem[x][spV] = sum;
	return sum;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>v[i]>>w[i];
	}
	res = dfs(1,m);
	cout<<res;
	return 0;
}

dfs记忆化搜索过程图 转存失败,建议直接上传图片文件 14届蓝桥杯 D题 飞机降落

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,n,a[15],b[15],c[15];
bool st[15];
bool sp ;
void dfs(ll x,ll t)//x表示第几架飞机起飞 
{
	if(sp) return ;
	if(x>n)
	{
		sp = true;
		return ;
	}
	for(int i=1;i<=n;i++)
	{
		if(!st[i])
		{
			if(b[i] + a[i] >= t)
			{
				st[i] = true;
				if(a[i]>t)
				dfs(x+1,a[i]+c[i]);
				else 
				dfs(x+1,t+c[i]);
				st[i] = false;
			}
			else return ;				
		}	
	}
}
int main()
{
	cin>>t;
	while(t--)
	{
		cin>>n;
		for(int i=1;i<=n;i++)
			cin>>a[i]>>b[i]>>c[i];
		
		for(int i=0;i<=15;i++)
			st[i] = false;
		sp = false;
		
		dfs(1,0);
		
		if(sp) cout<<"YES"<<"\n";
		else cout<<"NO"<<"\n";
	}
	return 0;
}
未完结,请期待后续更新