16-DFS(深度优先搜索算法)

Posted KKK3号

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了16-DFS(深度优先搜索算法)相关的知识,希望对你有一定的参考价值。

 DFS(深度优先算法)是常见的搜索算法,早期常用于需要搜索的地方,并且还拓展出很多其它算法。

深度优先算法(DFS)

DFS深度优先算法)是早期开发爬虫时常用的算法,它的搜索思路是从树根开始一直找直到找到树型数据结构的叶节点。以搜索一个节点数据为例,DFS算法会从树根开始,沿着一条路一直找到某个叶节点,如果还是找不到,它会回溯到上一个分支,沿着上一个分支节点又向下,直到找到下一个叶节点,如此往复,直至从最后一个叶节点返回到根节点。

由于DFS算法是一种灵活的算法,所以还是举个常用的例子来说明这个算法。 

八皇后问题

八皇后问题指的是将八个皇后棋子放到一个棋盘中,使得任意两个棋子不在同一横线、同一竖线、同一对角线和同一反对角线上。

下面我们来看一下如何用代码求解这个问题:

我们不妨将这个问题抽象出来,由于满足条件的排法所有的棋子一定会不同行,所以我们将这个问题看成是在8行里面每一行中任意一个格子放置一个棋子,然后再在其中选出排列出合适的情况。那么我们排列的过程就如下图所示:

那么我们可以利用一个树型的结构来描述这个问题,假设我们有一个二维数组,二维数组和棋盘位置对应,那么我们每次往棋盘里面填棋子的过程可以看成是往该二维数组里面特定位置填充元素的过程。填充的过程可以理解为从树根到树叶的一次搜索过程:

要想实现这个过程,我们首先需要一个二维数组,我们先创建该二维数组,然后在里面填入字符'o'代表初始时为空,并且设置一个输入n代表要开辟的棋盘的格数,这里的N表示上限:

#include<iostream>
using namespace std;

const int N = 20;
int n;
char chessboard[N][N];

int main()

	cin >> n;
	for (int i = 0; i < n; i++)
	
		for (int j = 0; j < n; j++)
			chessboard[i][j] = 'o';
	
	return 0;

 因为我们需要判断是否有处于同一列或者同一反对角线、同一对角线的元素,所以我们需要开辟三个数组:

bool col[N], diagonal[N], back_diagonal[N];

这些数组每个元素类型都是bool类型,数组的下标和对应的对角线如下:

之后我们就可以着手写DFS搜索算法了:

我们的DFS是一个使用递归的算法。它会自动帮我们从根节点开始往下创建节点这也是在逐步填棋子。

我们首先来看for循环,这个循环就是往下创建节点的核心,for循环每一层循环是在第u行从左到右移动格子的情况,里面的DFS递归是从上一行到下一行,所以递归每次u=u+1就代表向下移动一行。

void DFS(int u)

	for (int i = 0; i < n; i++)
	
		chessboard[u][i] = 'Q';
		DFS(u + 1);
	

为了判断当前的棋子是否和之前的棋子位置冲突了,我们就需要对三个布尔数组进行设置:当一个棋子放到某个位置时,这个位置对应的一列、对角线、反对角线的位置都被视作为true,表示以后棋子不能放到这个位置。

如果中间一个棋子放到这个位置,那么这个位置下面的情况都将不会被考虑,这种情况不符合if条件,代码会进行回溯,也就是说这条路就不会找到叶节点的位置,而会回溯到上一个节点的位置,并且重新向下找,这个过程就是“剪枝

void DFS(int u)


	for (int i = 0; i < n; i++)
	
		if (!col[i] && !diagonal[u + i] && !back_diagonal[n - u + i-1])
		
			chessboard[u][i] = 'Q';
			col[i] = diagonal[u + i] = back_diagonal[n - u + i-1] = true;
			DFS(u + 1);
			col[i] = diagonal[u + i] = back_diagonal[n - u + i-1] = false;
			chessboard[u][i] = 'o';
		
	

因为该程序设置的是如果中途不符合条件的路径会被剪掉,所以走到最后(u==n)的情况都是符合条件的。故我们将其打印出来输出。下图是代码执行过程的一部分:

所以总的代码如下:

#include<iostream>
using namespace std;

const int N = 20;
int n;
char chessboard[N][N];
bool col[N], diagonal[N], back_diagonal[N];

void DFS(int u)

	if (u == n)
	
		for (int i = 0; i < n; i++) cout << chessboard[i]<<endl;
		cout <<endl;
		return;
	
	for (int i = 0; i < n; i++)
	
		if (!col[i] && !diagonal[u + i] && !back_diagonal[n - u + i-1])
		
			chessboard[u][i] = 'Q';
			col[i] = diagonal[u + i] = back_diagonal[n - u + i-1] = true;
			DFS(u + 1);
			col[i] = diagonal[u + i] = back_diagonal[n - u + i-1] = false;
			chessboard[u][i] = 'o';
		
	


int main()

	cin >> n;
	for (int i = 0; i < n; i++)
	
		for (int j = 0; j < n; j++)
			chessboard[i][j] = 'o';
	
	DFS(0);
	return 0;

排列组合问题

提及DFS算法那必然需要提一下排列组合问题,这个问题和DFS算法的原理有密切的关系。我们知道,DFS算法是从树根开始向叶节点寻找,再从一个叶节点回溯到上一个节点再去找下一个节点的叶节点的动作。这种操作和我们求数字的排列原理很相似。如果我们将每搜索一个节点就理解为放一个数进去排列,那么一遍遍地搜索就是一次次地排列组合,下面是实现的代码。

#include<iostream>

using namespace std;

const int N = 10;
int n;
int arr[N];
bool flag[N];

void DFS(int u)

	if (u == n)
	
		for (int i = 0; i < n; i++)
			cout << arr[i];
		cout << endl;
	
	for (int i = 0; i < n; i++)
	
		if (!flag[i])
		
			arr[u] = i;
			flag[i] = true;
			DFS(u + 1);
			flag[i] = false;
		
	



int main()

	cin >> n;
	DFS(0);
	return 0;

数据结构与算法图遍历算法 ( 深度优先搜索 DFS | 深度优先搜索和广度优先搜索 | 深度优先搜索基本思想 | 深度优先搜索算法步骤 | 深度优先搜索理论示例 )

文章目录





一、深度优先搜索 DFS




1、深度优先搜索和广度优先搜索


图 的 遍历 就是 对 图 中的 结点 进行遍历 , 遍历 结点 有如下两种策略 :

  • 深度优先搜索 DFS
  • 广度优先搜索 BFS

2、深度优先搜索基本思想


" 深度优先搜索 " 英文名称是 Depth First Search , 简称 DFS ;

DFS 基本思想 :

  • 访问第一个邻接结点 :起始点 出发 , 该 起始点 可能有 若干 邻接结点 , 访问 第一个 邻接结点 , 然后 再访问 第一个 邻接结点 的 第一个邻接结点 , 每次都访问 当前结点 的 第一个邻接结点 ;
  • 访问策略 : 优先 向 纵向遍历 , 不是 对 当前结点 的所有 邻接结点 进行 横向遍历 ;
  • 递归过程 : 上述 DFS , 每次访问 起始点 的 第一个邻接结点 后 , 又将 该 第一个邻接结点 作为 新的起始点 继续向下访问 , 该过程是一个递归过程 ;

3、深度优先搜索算法步骤


深度优先搜索算法步骤 :

  • ① 访问初始结点 : 访问 初始结点 v , 并将该 初始结点 v 标记为 " 已访问 " ;
  • ② 查找邻接节点 : 查找 初始结点 v 的 第一个 邻接节点 w ;
  • ③ 邻接节点是否存在 :
    • 如果 w 结点存在 , 执行 ④ 操作 判断该 结点 是否被访问 ;
    • 如果 w 结点 不存在 , 回到 ① 查找 初始结点 v 的下一个 邻接节点 ;
  • ④ 邻接节点是否被访问 :
    • 如果 w 结点存在 并且 没有被访问 , 那么 对 w 结点 进行 深度优先遍历 , 将 w 结点 作为 新的 初始结点 v , 从 ① 步骤开始执行 ;
    • 如果 w 结点存在 但是 被访问了 , 那么 查找 w 结点的 下一个 邻接节点 , 转到步骤 ③ 执行 ;




二、深度优先搜索示例 ( 理论 )



以下图为例 , 说明 DFS 搜索步骤 ; 初始结点 A ;

初始结点 为 A , 开始进行 DFS :


1、第一轮递归


访问 初始结点 A , 并将该 初始结点 A 标记为 " 已访问 " ;

查找 初始结点 A 的 第一个 邻接节点 B ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 A 的第一个 邻接结点 , 从 A 的那一排 第 0 排开始查找 , 第一个为 1 的元素就是 对应 第一个 邻接结点 )

查询邻接节点 B 是否存在 ; 邻接节点 B 结点存在 ;

查询邻接节点 B 是否被访问 ; 邻接节点 B 结点存在 并且 没有被访问 , 那么 对 邻接节点 B 结点 进行 深度优先遍历 , 将 邻接节点 B 结点 作为 新的 初始结点 , 从 ① 步骤开始执行 ;


2、第二轮递归


访问 初始结点 B , 并将该 初始结点 B 标记为 " 已访问 " ;

查找 初始结点 B 的 第一个 邻接节点 A ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 B 的第一个 邻接结点 , 从 B 的那一排 第 1 排开始查找 , 第一个为 1 的元素 对应的 是 A 节点 ;

查询邻接节点 A 是否存在 ; 邻接节点 A 结点存在 ;

查询邻接节点 A 是否被访问 ; 邻接节点 A 结点 存在 但是 被访问了 , 那么 查找 B 结点的 下一个 邻接节点 , 转到步骤 ③ 执行 ;

查找 结点 B 的 第二个 邻接节点 C ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 B 的第二个 邻接结点 , 从 B 的那一排 第 1 排开始查找 , 第二个为 1 的元素 对应的 是 C 节点 ;

查询邻接节点 C 是否存在 ; 邻接节点 C 结点存在 ;

查询邻接节点 C 是否被访问 ; 邻接节点 C 结点存在 并且 没有被访问 , 那么 对 邻接节点 C 结点 进行 深度优先遍历 , 将 邻接节点 C 结点 作为 新的 初始结点 , 从 ① 步骤开始执行 ;


3、第三轮递归


访问 初始结点 C , 并将该 初始结点 C 标记为 " 已访问 " ;

查找 初始结点 C 的 第一个 邻接节点 A ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 C 的第一个 邻接结点 , 从 C 的那一排 第 2 排开始查找 , 第一个为 1 的元素就是 对应 第一个 邻接结点 ;

查询邻接节点 A 是否存在 ; 邻接节点 A 结点存在 ;

查询邻接节点 A 是否被访问 ; 邻接节点 A 结点 存在 但是 被访问了 , 那么 查找 C 结点的 下一个 邻接节点 , 转到步骤 ③ 执行 ;

查找 结点 C 的 第二个 邻接节点 B ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 C 的第二个 邻接结点 , 从 C 的那一排 第 2 排开始查找 , 第二个为 1 的元素就是 对应 第二个 邻接结点 ;

查询邻接节点 B 是否存在 ; 邻接节点 B 结点存在 ;

查询邻接节点 B 是否被访问 ; 邻接节点 B 结点 存在 但是 被访问了 , 那么 查找 C 结点的 下一个 邻接节点 , 转到步骤 ③ 执行 ;

查找 结点 C 的 第三个 邻接节点 ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 C 的第三个 邻接结点 , 从 C 的那一排 第 2 排开始查找 , 第三个为 1 的元素就是 对应 第三个 邻接结点 ;

C 的第三个 邻接结点 不存在 , 回到 ① 查找 初始结点 B 的下一个 邻接节点 ;


4、第四轮递归


在 第二轮递归 中 , 已经查找了 B 的 2 个邻接结点了 , 开始查找 B 的 第 3 个邻接结点 ;

查找 结点 B 的 第三个 邻接节点 D ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 B 的第三个 邻接结点 , 从 B 的那一排 第 1 排开始查找 , 第三个为 1 的元素 对应的 是 D 节点 ;

查询邻接节点 D 是否存在 ; 邻接节点 D 结点存在 ;

查询邻接节点 D 是否被访问 ; 邻接节点 D 结点存在 并且 没有被访问 , 那么 对 邻接节点 D 结点 进行 深度优先遍历 , 将 邻接节点 D 结点 作为 新的 初始结点 , 从 ① 步骤开始执行 ;


5、第五轮递归


访问 初始结点 D , 并将该 初始结点 D 标记为 " 已访问 " ;

查找 初始结点 D 的 第一个 邻接节点 B ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 D 的第一个 邻接结点 , 从 D 的那一排 第 3 排开始查找 , 第一个为 1 的元素就是 对应 第一个 邻接结点 B ;

查询邻接节点 B 是否存在 ; 邻接节点 B 结点存在 ;

查询邻接节点 B 是否被访问 ; 邻接节点 B 结点 存在 但是 被访问了 , 那么 查找 D 结点的 下一个 邻接节点 , 转到步骤 ③ 执行 ;

查找 结点 D 的 第二个 邻接节点 ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 D 的第二个 邻接结点 , 从 D 的那一排 第 3 排开始查找 , 第二个为 1 的元素就是 对应 第二个 邻接结点 ;

D 的第三个 邻接结点 不存在 , 回到 ① 查找 初始结点 B 的下一个 邻接节点 ;


6、第六轮递归


在 第四轮递归 中 , 已经查找了 B 的 3 个邻接结点了 , 开始查找 B 的 第 4 个邻接结点 ;

查找 结点 B 的 第四个 邻接节点 E ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 B 的第四个 邻接结点 , 从 B 的那一排 第 1 排开始查找 , 第四个为 1 的元素 对应的 是 E 节点 ;

查询邻接节点 E 是否存在 ; 邻接节点 E 结点存在 ;

查询邻接节点 E 是否被访问 ; 邻接节点 E 结点存在 并且 没有被访问 , 那么 对 邻接节点 E 结点 进行 深度优先遍历 , 将 邻接节点 E 结点 作为 新的 初始结点 , 从 ① 步骤开始执行 ;


7、第七轮递归


访问 初始结点 E , 并将该 初始结点 E 标记为 " 已访问 " ;

查找 初始结点 E 的 第一个 邻接节点 B ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 E 的第一个 邻接结点 , 从 E 的那一排 第 4 排开始查找 , 第一个为 1 的元素就是 对应 第一个 邻接结点 B ;

查询邻接节点 B 是否存在 ; 邻接节点 B 结点存在 ;

查询邻接节点 B 是否被访问 ; 邻接节点 B 结点 存在 但是 被访问了 , 那么 查找 D 结点的 下一个 邻接节点 , 转到步骤 ③ 执行 ;

查找 结点 B 的 第五个 邻接节点 ;

  • 邻接结点选择 : 这里的 第一个邻接节点 选择 , 是在内存数据 邻接表 中排列在首位 0 索引的节点 , 或者 与 邻接矩阵 中 元素位置 有关 , 没有其它意义 ;
  • 在下面的 邻接矩阵 中 , 查找 B 的第五个 邻接结点 , 从 B 的那一排 第 3 排开始查找 , 第五个为 1 的元素就是 对应 第二个 邻接结点 ;

B 的第五个 邻接结点 不存在 , 回到 ① 查找 初始结点 A 的下一个 邻接节点 ;

继续回溯到 A 结点 , 查找 A 结点的 第二个 邻接结点 C , 然后 以 C 为初始结点继续进行遍历 , 进行回溯 , 所有的结点都已经遍历 , 递归结束 ;

以上是关于16-DFS(深度优先搜索算法)的主要内容,如果未能解决你的问题,请参考以下文章

数据结构与算法图遍历算法 ( 深度优先搜索 DFS | 深度优先搜索和广度优先搜索 | 深度优先搜索基本思想 | 深度优先搜索算法步骤 | 深度优先搜索理论示例 )

学懂算法------深度优先搜索(DFS)

深度优先搜索算法解释下?

基本算法——深度优先搜索(DFS)和广度优先搜索(BFS)

算法题——深度优先搜索与广度优先搜索

快速排序模板秦九昭算法模板深度优先搜索DFS广度优先搜索BFS图的遍历逆元中国剩余定理斯特林公式