目录
深度优先搜索(Depth-First Search,DFS)是一种用于图和树等数据结构中的遍历或搜索算法
它通过尽可能深地探索图的分支,直到不能再继续为止,然后回溯并继续探索其他分支
- 从起始节点开始,标记该节点为已访问
- 探索从当前节点出发的每个相邻节点
- 对于每个相邻节点,如果它还没有被访问,就递归地访问它,然后继续深度探索
- 如果没有未访问的相邻节点,或者达到终止条件(自己设置的,例如找到目标节点或遍历完整个图),则回溯到上一个节点,继续查找未访问的节点
- 重复以上步骤,直到所有节点都被访问或找到了解决问题的路径
遍历图/树
- DFS 可以用来访问并处理图或树中的所有节点
- 通过深度优先的方式,它会首先访问一个节点,然后递归地访问其相邻节点,以此类推,直到所有节点都被访问
查找路径/解决问题
- DFS 可以用于查找从一个节点到另一个节点的路径
- 或者用于解决搜索问题,如迷宫问题、拓扑排序、生成树等
下面用迷宫作为例子,展示一下如何用递归/非递归版本的dfs解决迷宫问题
首先,先定义迷宫
// 定义迷宫的大小 const int m = 5; const int n = 5; // 定义方向,上、右、下、左 int dir[4][2] = { {-1, 0}, {0, 1}, {1, 0}, {0, -1} }; // 定义三元组结构体 struct Triple { int x, y, dir; //横坐标,纵坐标,下一个位置的方向 Triple(int _x, int _y, int _dir) : x(_x), y(_y), dir(_dir) {} };这里,还引入了一个新变量,dir -- 记录下一个位置的方向(别问为什么,问就是老师布置的作业中有这个)
虽然是迷宫问题,但实际上其实就是找能走的位置,然后将它记录下来
- 而我们dfs是需要回溯的,也就是会将最后一个元素删掉,而且记录元素都是插在尾巴上
- 所以!栈就是我们最好的选择
由于我们要记录下一个位置的方向,但是我们又无法提前预知到
- 虽然下一个方向的不知道,但我们可以知道这次要选择的方向(也就是上一个位置的下一个方向)
- 所以,存储的时候,我们要插入两次
- 一次是上一个位置+它对应的下一个方向(这个是我们最终路径上有效的值)
- 一次是这次的位置+随便一个值(因为这个是为了让我们取位置用的,方向没用到)
- 注意第二次插入的临时值,在下次探索的时候,需要先pop掉,再进行操作
还有就是,我们这里存进去的下标都是从1开始的,但实际使用的时候需要-1,因为计算机里的下标是从0开始的,所以会有相互转换的处理
- // 非递归DFS算法
- bool solveMaze(stack
& s, int maze[m][n],int sx, int sy, int tx, int ty) { - s.push(Triple(sx, sy, 0));
- maze[sx-1][sy-1] = 2;
-
- while (!s.empty()) {
- Triple cur = s.top();
- int x = cur.x - 1, y = cur.y - 1, d = 0;
-
- if (x == tx - 1 && y == ty - 1) {
- return true;
- }
-
- bool found = false;
- while (d < 4) {
- int nx = x + dir[d][0];
- int ny = y + dir[d][1];
-
- if (nx >= 0 && nx < m && ny >= 0 && ny < n && maze[nx][ny] == 0) {
- s.pop(); //说明上一个位置有效,所以先把那个临时值pop掉
- found = true;
- s.push(Triple(x+1, y+1, d)); //有效值
- s.push(Triple(nx + 1, ny + 1, d)); //临时值
- x = nx;
- y = ny;
- maze[x][y] = 2; // 标记为已访问过的路径
- break;
- }
- else {
- d++; // 尝试下一个方向
- }
- }
- if (found == false) {
- s.pop(); //四个方向都不行,说明这个位置不对
- }
- }
-
- return false; // 没有找到通路
- }
因为递归版本老师要求我们求出所有通路,所以,元素结构也要变一下,不需要记录那个方向了
- struct Path {
- vector
int, int>> steps; - };
还是一样的思路
每次取上一个位置的坐标进行四个方向的探索,如果可以,就把这个结点插入,并且以该结点为基础进行下一次的探索
- int a[m][n]; //用于计算类型(其实可以不用的(挠头))
- void findPaths(Path& currentPath,decltype(a)maze, int sx, int sy, int tx, int ty) {
- if (sx == tx && sy == ty ) {
- // 找到一条通路,输出路径
- cout << "通路:";
- for (const auto& step : currentPath.steps) {
- cout << "(" << step.first << "," << step.second << ") ";
- }
- cout << endl;
- currentPath.steps.pop_back(); //把终点pop掉
- return;
- }
-
-
- for (int d = 0; d < 4; d++) {
- int size = currentPath.steps.size();
- if (size == 0) {
- break;
- }
- int x = currentPath.steps[size - 1].first, y = currentPath.steps[size - 1].second;
- int nx = x + dir[d][0]-1;
- int ny = y + dir[d][1]-1;
-
- if (nx >= 0 && nx < m && ny >= 0 && ny < n && maze[nx][ny] == 0) {
- // 标记当前位置已访问
- maze[nx][ny] = 2;
-
- // 添加当前步骤到路径
- currentPath.steps.push_back(make_pair(nx+1, ny+1));
-
- // 继续搜索下一步
- findPaths( currentPath,maze, nx+1, ny+1, tx,ty);
-
- // 恢复状态,回溯
- if (currentPath.steps.size() != 0) {
- currentPath.steps.pop_back();
- }
- maze[nx][ny] = 0;
- }
- }
- }