钰的DFS学习笔记

本文介绍了深度优先搜索(DFS)算法,它是图论中的搜索算法,可全局搜索,因时间复杂度高也叫暴力搜索。还说明了适用场景,包括地图型和数据型题目,并给出相关例题链接,最后列举了全排列、高手散步等多个使用DFS解决的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

深度优先搜索(Depth-First-Search)

dfs是什么?

DFS是图论里面的一种搜索算法,他可以由一个根节点出发,遍历所有的子节点,进而把图中所有的可以构成树的集合都搜索一遍,达到全局搜索的目的。所以很多问题都可以用dfs来遍历每一种情况,从而得出最优解,但由于时间复杂度太高,我们也叫做暴力搜索

啥时候用dfs?

当题目有如下特征,并且数据范围较小(时间复杂度太高)

 1 . 地图型:这种题型将地图输入,要求完成一定的任务。因为地图的存在。使得题意清楚形象化,容易理清搜索思路

        AOJ 869-迷宫(遍历地图,四向搜索)   https://blog.youkuaiyun.com/chen_yuazzy/article/details/73656668
        HDU 1035-Robot Motion(指定方向搜索,迷路(循环)判断)   http://acm.hdu.edu.cn/showproblem.php?pid=1035
        HDU 1045-Fire Net(check函数,回溯)  http://acm.hdu.edu.cn/showproblem.php?pid=1045
        HDU 1010-Tempter of the Bone(奇偶剪枝,回溯)  http://acm.hdu.edu.cn/showproblem.php?pid=1010

        POJ 1031棋盘问题  (类似八皇后问题)http://poj.org/problem?id=132 

    2 . 数据型:这种题型没有给定地图,一般是一串数字或字母,要求按照一定的任务解题。相对于地图型,这种题型较为抽象,需要在数据中进行搜索。数据以数组的形式存储,那么只要将数组也当作一张图来进行搜索就可以了。

        HDU 1016-Prime Ring Problem(回溯、素数筛)
        HDU 1258-Sum It Up(双重DFS递归,去重技巧)
        HDU 1015-Safecraker(回溯,字符处理)
        HDU 2676-Sudoku(抽象,回溯)

怎么用dfs?

结合下图模板及例题

int check(参数)
{
    if(满足条件)
        return 1;
    return 0;
}
 
void dfs(int step)
{
        判断边界
        {
            相应操作
        }
        尝试每一种可能
        {
               满足check条件
               标记
               继续下一步dfs(step+1)
               恢复初始状态(回溯的时候要用到)
        }
}   

思维导图(图论里面的沧海一粟/(ㄒoㄒ)/~~) 

P1706 全排列问题

题目描述

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

输入格式

一个整数 nn。

输出格式

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

每个数字保留 55 个场宽。

输入输出样例

输入 #1复制

3

输出 #1复制

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

说明/提示

1 \leq n \leq 91≤n≤9

#include<bits/stdc++.h>
using namespace std;
int a[101],b[101],n;
void print()
{
	for(int i=1;i<=n;i++)
	cout<<setw(5)<<a[i];
	cout<<endl;
}
void dfs(int i)
{
	if(i==n+1)// 判断边界 
	{
		print();
		return ;
	}
	for(int j=1;j<=n;j++)//尝试每一种可能 
	{
		if(b[j]==0)//满足dfs条件 
		{
			a[i]=j;
			b[j]=1;//标记 
			dfs(i+1);//继续下一步 
			b[j]=0;//恢复初始条件,以便于回溯 (虚晃一枪) 
		}
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin>>n;
	dfs(1);
	return 0;
}

P1294 高手去散步 

题目背景

高手最近谈恋爱了。不过是单相思。“即使是单相思,也是完整的爱情”,高手从未放弃对它的追求。今天,这个阳光明媚的早晨,太阳从西边缓缓升起。于是它找到高手,希望在晨读开始之前和高手一起在鳌头山上一起散步。高手当然不会放弃这次梦寐以求的机会,他已经准备好了一切。

题目描述

鳌头山上有n个观景点,观景点两两之间有游步道共m条。高手的那个它,不喜欢太刺激的过程,因此那些没有路的观景点高手是不会选择去的。另外,她也不喜欢去同一个观景点一次以上。而高手想让他们在一起的路程最长(观景时它不会理高手),已知高手的穿梭机可以让他们在任意一个观景点出发,也在任意一个观景点结束。

输入格式

第一行,两个用空格隔开的整数n、m. 之后m行,为每条游步道的信息:两端观景点编号、长度。

输出格式

一个整数,表示他们最长相伴的路程。

输入输出样例

输入 #1复制

4 6
1 2 10
2 3 20
3 4 30
4 1 40
1 3 50
2 4 60

输出 #1复制

150

说明/提示

对于100%的数据:n≤20,m≤50,保证观景点两两之间不会有多条游步道连接.

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int g[N][N],dist,maxn=-10,n,m,x,y,z;
bool vis[N];
void dfs(int st)
{
	for(int i=1;i<=n;i++)
	{
		if(g[st][i]&&!vis[i])
		{
			vis[i]=1;
			dist+=g[st][i];
			dfs(i);//由于i自加所以长得和别人不同 
			dist-=g[st][i];//恢复初始状态(回溯的时候要用到)
		}
	}
	maxn=max(maxn,dist);//更新最大值 
	vis[st]=0;
	return ;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y>>z;
		g[x][y]=z;
		g[y][x]=z; // 双向图哦 
	}
	for(int i=1;i<=n;i++)
	{
		vis[i]=1;
		dfs(i);
		memset(vis,0,sizeof(vis));//又回到最初的起点 
	}
	cout<<maxn<<endl;
	return 0;
 } 

P2196 挖地雷 

题目描述

在一个地图上有NN个地窖(N \le 20)(N≤20),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径。当地窖及其连接的数据给出之后,某人可以从任一处开始挖地雷,然后可以沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使某人能挖到最多的地雷。

输入格式

有若干行。

第11行只有一个数字,表示地窖的个数NN。

第22行有NN个数,分别表示每个地窖中的地雷个数。

第33行至第N+1N+1行表示地窖之间的连接情况:

第33行有n-1n−1个数(00或11),表示第一个地窖至第22个、第33个、…、第nn个地窖有否路径连接。如第33行为1 1 0 0 0 … 011000…0,则表示第11个地窖至第22个地窖有路径,至第33个地窖有路径,至第44个地窖、第55个、…、第nn个地窖没有路径。

第44行有n-2n−2个数,表示第二个地窖至第33个、第44个、…、第nn个地窖有否路径连接。

… …

第n+1n+1行有11个数,表示第n-1n−1个地窖至第nn个地窖有否路径连接。(为00表示没有路径,为11表示有路径)。

输出格式

有两行

第一行表示挖得最多地雷时的挖地雷的顺序,各地窖序号间以一个空格分隔,不得有多余的空格。

第二行只有一个数,表示能挖到的最多地雷数。

输入输出样例

输入 #1复制

5
10 8 4 7 6
1 1 1 0
0 0 0
1 1
1

输出 #1复制

1 3 4 5
27
#include<bits/stdc++.h>
using namespace std;
bool f[21][21],b[21];//f记录是否有路径相连,b检测这点是否走过 
int a[21];//记录地雷数
int path[21],ans[21],cnt;//path记录路径,ans记录答案,cnt记录走了多少个点
int n;
int maxn;// 最大值 
bool check(int x)
{
	for(int i=1;i<=n;i++)
	{
		if(f[x][i]&&!b[i]) return false;//不满足条件返回false 
	}
	return true;
}
void dfs(int x,int stp,int sum)
{
	if(check(x))//判断边界 
	{
		if(maxn<sum)
		{
			maxn=sum;
			cnt=stp;
			for(int i=1;i<=stp;i++)
			ans[i]=path[i];
		}//相应操作 
		return ;
	}
	for(int i=1;i<=n;i++)// 尝试每一种可能 
	{
		if(f[x][i]&&!b[i])//满足check条件 
		{
			b[i]=1;//标记 
			path[stp+1]=i;
			dfs(i,stp+1,sum+a[i]);//继续下一步dfs 
			b[i]=0;//恢复初始状态,准备回溯 
		}
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];//输入地雷数量 
	for(int i=1;i<n;i++)
	for(int j=i+1;j<=n;j++)
	cin>>f[i][j];//注意这里为单向边,区别于爬山的双向 
	for(int i=1;i<=n;i++)
	{
		b[i]=1;
		path[1]=i;
		dfs(i,1,a[i]);
		b[i]=0;
		}	
	for(int i=1;i<=cnt;i++)
	cout<<ans[i]<<' ';
	cout<<endl<<maxn;
	return 0;
}

P1101 单词方阵 

题目描述

给一n \times nn×n的字母方阵,内可能蕴含多个“yizhong”单词。单词在方阵中是沿着同一方向连续摆放的。摆放可沿着 88 个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间可以交叉,因此有可能共用字母。输出时,将不是单词的字母用*代替,以突出显示单词。例如:

输入:
    8                     输出:
    qyizhong              *yizhong
    gydthkjy              gy******
    nwidghji              n*i*****
    orbzsfgz              o**z****
    hhgrhwth              h***h***
    zzzzzozo              z****o**
    iwdfrgng              i*****n*
    yyyygggg              y******g

输入格式

第一行输入一个数nn。(7 \le n \le 1007≤n≤100)。

第二行开始输入n \times nn×n的字母矩阵。

输出格式

突出显示单词的n \times nn×n矩阵。

输入输出样例

输入 #1复制

7
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa
aaaaaaa

输出 #1复制

*******
*******
*******
*******
*******
*******
*******

输入 #2复制

8
qyizhong
gydthkjy
nwidghji
orbzsfgz
hhgrhwth
zzzzzozo
iwdfrgng
yyyygggg

输出 #2复制

*yizhong
gy******
n*i*****
o**z****
h***h***
z****o**
i*****n*
y******g
#include<bits/stdc++.h>
using namespace std;
const int maxn=110;
const string cmp="yizhong";
int dx[]={1,1,1,-1,-1,-1,0,0};
int dy[]={1,-1,0,1,-1,0,1,-1};
char a[maxn][maxn],ans[maxn][maxn];
int mark[maxn][maxn],n;
void dfs(int x,int y)
{
	for(int i=0;i<8;i++)
	{
		int flag=1;
		for(int j=1;j<=6;j++)
		{
		int xx=x+j*dx[i];
		int yy=y+j*dy[i];//虽然不是按什么条件深搜的,但这题一路搜到底很有dfs精神 
		if(xx<1||xx>n||yy<1||yy>n)
		{
			flag=0;
			break;
		}
		if(cmp[j]!=a[xx][yy])
		{
			flag=0;
			break;
		}
	}
	
	if(flag==0) continue;
	for(int j=0;j<=6;j++)//一定要从0开始,不然y会不见,别问我怎么知道的 
	{
		int xx=x+j*dx[i];
		int yy=y+j*dy[i];
		ans[xx][yy]=a[xx][yy];
	}
}
return ;
}
int main()
{
	ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		cin>>a[i][j];
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		if(a[i][j]=='y') dfs(i,j);
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			if(!ans[i][j]) ans[i][j]='*';
			cout<<ans[i][j];
		}
		cout<<endl;
	}
	return 0;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值