抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

DFS的介绍

DFS(深度优先搜索,Depth-First Search)是一种用于遍历或搜索树或图的算法。
它从一个节点开始,尽可能深地搜索树的分支,直到到达叶子节点(没有子节点的节点),然后回溯到上一个节点,继续搜索其他分支。
这个过程会一直进行,直到所有可能的分支都被探索完毕。

工作原理

DFS的工作原理可以总结为以下几个步骤

  1. 选择一个起始节点:从树或图的某个节点开始。
  2. 探索尽可能深的分支:从当前节点开始,选择一个未被访问过的邻接节点,然后递归地在该节点上执行DFS。
  3. 回溯:当当前节点的所有邻接节点都被访问过,或者到达了叶子节点时,回溯到上一个节点。
  4. 重复:重复步骤2和3,直到所有节点都被访问过。

DFS的特点

  1. 栈的使用:在实现DFS时,通常使用栈数据结构来存储节点。在递归实现中,调用栈隐式地充当了栈的角色。
  2. 时间复杂度:对于有V个顶点和E条边的图,DFS的时间复杂度通常是O(V + E)。
  3. 空间复杂度:在最坏的情况下,DFS的空间复杂度也是O(V),因为可能需要存储整个路径上的节点。
  4. 路径搜索:DFS非常适合于寻找从起点到终点的路径,尤其是在图不是非常大的情况下。
  5. 连通性问题:DFS可以用来检测图的连通性,例如,检查一个图是否是强连通的。

图示示例

下面是一个简单的图示:




节点1为根节点,也叫起始节点



那么节点1向下遍历,直到遍历到第一个没有子节点的节点4

因为4已经没有子节点,已经是叶子节点,所以无法再向下遍历,所以回溯到上一个它的父节点3,并对节点4作标记,节点3接着向下遍历未被标记的子节点5

因为5同样是叶子节点,遍历有到达了最深层,所以继续回溯到父节点3,同时标记5,我们发现3也没有未被标记的子节点了,无法再向下遍历,那么3接着向上回溯到父节点2,同时对3作标记,继续遍历2的未被标记的子节点6



接下来过程:2→6→7,节点7向上回溯到6并标记,节点6向下遍历子节点:6→8,然后连续向上回溯 8→6→2→1 直到回溯到根节点1,发现有可以向下遍历的子节点

接着按照上述模式进行剩余的遍历,完成整张图的遍历,图的序号即为遍历的顺序


例题:马的遍历

这是Luogu上一道搜索的例题 https://www.luogu.com.cn/problem/P1443
接着给出示例代码

1
2
3
4
5
6
7
8
9
#include<bits/stdc++.h> //万能头文件 
using namespace std; //引用stl库
const int MAXN = 1000009; //定义一个大数
bool is[401][401]; //对一个点(i,j)标记是否已经遍历过
int num[401][401]; //存储起始点(x,y)到每个点(i,j)的距离
int n,m,begin_x,begin_y;
int move_x[8] = {1,1,2,2,-1,-1,-2,-2};
int move_y[8] = {2,-2,1,-1,2,-2,1,-1};
//马的八种移动方式对应的(x,y)坐标的变化

初始工作

1
2
3
4
5
6
7
8
9
int main(){
cin>>n>>m>>begin_x>>begin_y;
memset(is,true,sizeof(is));
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) num[i][j] = MAXN;
dfs(begin_x,begin_y,0);
print();
return 0;
}

main函数的内容如上,初始化标记数组is,为全部未访问过,num[i][j]初始化为一个极大的数
dfs(int x,int y,int k)为一个递归函数 x,y:当前点位的横坐标; k:走到目前点位的步数
dfs(begin_x,begin_y,0) 即从初始点位开始dfs,步数为0(马到起始点的步数为0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void dfs(int x,int y,int k)
{
if(x <= 0||y <= 0||num[x][y] < k||x > n || y > m) return;
//如果遍历到的坐标点不在方格图内 即超过了图的横纵坐标或者小于等于0(方格图横纵坐标范围为1~n,1~m) 回溯
//num[x][y] < k : 如果该点位的答案已经小于现有步数,那么k已经超过了答案,没有必要进行 回溯
num[x][y] = k;
//更新num[x][y]
for(int i = 0 ;i < 8; i++) //枚举八个走马后的可能坐标值
{
int tx = move_x[i] + x;
int ty = move_y[i] + y;
//(tx,ty) : 走马后的坐标
is[x][y] = false;
dfs(tx,ty,k + 1);
//向后遍历,步数加1,打标记,防止父子节点反复遍历死循环
is[x][y] = true; //恢复标记,可以再次访问
}
}

dfs内容如上

接着我们需要对题目所要求的输出进行修改,有格式要求

1
2
3
4
5
6
7
8
9
10
11
12
void print()
{
for(int i = 1 ; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(num[i][j] == MAXN) cout<<"-1 ";
else cout<<num[i][j]<<" ";
}
cout<<endl;
}
}

当然你也可以采取下面修改场宽的方式进行所要求格式的输出

1
printf("%-5d",num[i][j]);

全部代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1000009;
bool is[401][401];
int num[401][401];
int n,m,begin_x,begin_y;
int move_x[8] = {1,1,2,2,-1,-1,-2,-2};
int move_y[8] = {2,-2,1,-1,2,-2,1,-1};
void dfs(int x,int y,int k)
{
if(x <= 0||y <= 0||num[x][y] < k||x > n || y > m) return;
num[x][y] = k;
for(int i = 0 ;i < 8; i++)
{
int tx = move_x[i] + x;
int ty = move_y[i] + y;
is[x][y] = false;
dfs(tx,ty,k + 1);
is[x][y] = true;
}
}
void print()
{
for(int i = 1 ; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(num[i][j] == MAXN) cout<<"-1 ";
else cout<<num[i][j]<<" ";
}
cout<<endl;
}
}
int main(){
cin>>n>>m>>begin_x>>begin_y;
memset(is,true,sizeof(is));
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++) num[i][j] = MAXN;
dfs(begin_x,begin_y,0);
print();
return 0;
}

在代码中,DFS被用来计算从起点开始,到达每个位置所需的最少移动次数。虽然最终结果可能是正确的,但由于DFS的递归性质,它可能会导致大量的函数调用开销,并且可能会在没有找到更优解的情况下继续深入搜索,从而导致效率较低。

接下来 将引入在这个场景下更为合适的另一种搜索方法,宽(广)度优先搜索(BFS)

评论