Bootstrap

数据结构与算法-搜索-dfs(连通性,回溯)

dfs

深度优先搜索(Depth-First Search,DFS)是一种用于遍历或搜索树或图的算法。该算法沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点 v 的所在边都己被探寻过,搜索将回溯到发现节点 v 的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。

算法思想

DFS 算法的核心思想是从一个起始节点开始,沿着一条路径尽可能深地访问节点,直到无法继续,然后回溯到上一个节点,继续探索其他路径。这种搜索方式类似于树的先序遍历。

应用场景

连通性检测:判断图中任意两个节点是否连通,或者找出图中的所有连通分量。例如,在社交网络中判断两个用户是否存在间接联系。

拓扑排序:对有向无环图(DAG)进行拓扑排序,用于解决任务调度等问题。比如在课程安排中,确定课程的先后学习顺序。

路径搜索:寻找图中两个节点之间的路径。例如在迷宫游戏中寻找从起点到终点的路径。

图的遍历:遍历图中的所有节点,例如在游戏开发中遍历地图中的所有区域。

注意事项

避免重复访问:在 DFS 过程中,需要使用一个集合来记录已经访问过的节点,避免重复访问,否则可能会陷入无限循环。

递归深度:递归实现的 DFS 可能会导致栈溢出,特别是在图的深度较大时。可以使用迭代实现来避免这个问题。

习题1(迷宫)连通性:

分析:

这是一道典型的迷宫路径搜索问题,可使用深度优先搜索(DFS)或广度优先搜索(BFS)算法解决,以下从这两种思路进行分析:

DFS 思路分析

状态表示:迷宫的每个格子对应图中的一个节点,节点状态包含其在迷宫中的坐标(i,j)

初始状态:将起点 A 的坐标作为起始节点。

状态转移:对于当前节点,依次尝试向东南西北四个方向移动,若移动后的坐标在迷宫范围内且对应格子为.(可通行),并且未被访问过,则将其作为新节点进行递归搜索。

终止条件:当搜索到终点 B 时,返回 True 表示能到达;当遍历完所有可达节点仍未找到终点 B,返回 False 表示不能到达。如果起点或终点对应格子为#,直接返回 False。

伪代码:

如果要记录路径,就需要额外维护两个数组(ans:记录答案路径数组,tmp:记录当前搜索路径的数组)

习题2(红与黑)连通数量问题;


分析:

这是一道典型的基于图的遍历问题,可通过深度优先搜索(DFS)

可以将房间中的每一块瓷砖看作图中的一个节点,相邻的黑色瓷砖之间存在边相连。目标是从给定的起始黑色瓷砖节点出发,遍历所有与之相连的黑色瓷砖节点,并统计节点数量。

DFS 思路分析

状态表示:每个瓷砖节点的状态由其在房间中的坐标(i,j)表示。

初始状态:以起始的黑色瓷砖坐标作为开始节点。

状态转移:从当前节点出发,向其上下左右四个相邻方向进行探索。如果相邻方向的瓷砖在房间范围内且为黑色瓷砖(未被访问过),则将其作为新的节点继续递归搜索。

终止条件:当没有可继续探索的相邻黑色瓷砖节点时,回溯到上一个节点继续探索其他方向。当所有可达的黑色瓷砖节点都被访问过,搜索结束。

伪代码:

习题3(马走日)回溯类;

分析:

这是一个基于图论的遍历计数问题,可利用深度优先搜索(DFS)算法来解决,以下是详细分析:

将棋盘上的每个格子看作图中的一个节点,马按照 “日” 字移动规则,从一个格子到另一个格子的移动视为图中节点间的边。目标是从给定的初始位置出发,使用 DFS 遍历所有节点,统计能遍

历完整个棋盘的路径数量。

算法思路

状态表示:用三维数组visited[n][m][state]记录每个格子的状态,n和m分别是棋盘的行数和列数,state表示是否被访问过。此外,用一个变量path_count记录满足条件的路径数量。

初始状态:将马的初始位置标记为已访问,path_count初始化为 0。

状态转移:从当前位置出发,根据马走 “日” 字的 8 种可能移动方向,计算新的位置坐标。若新位置在棋盘内且未被访问过,则递归调用 DFS 函数继续探索,探索结束后回溯,将该位置的访问状态重置为未访问,以便其他路径可以访问。

终止条件:当访问的格子数量等于棋盘格子总数n * m时,说明找到了一条遍历整个棋盘的路径,path_count加 1。

伪代码:

习题4(分成互质组)回溯;

分析:

DFS 思路

可以把分组过程看作是一个状态空间的搜索过程。每个状态表示当前已经分好的组以及还未分组的数。

初始状态:所有数都未分组,组为空。

状态转移:对于当前未分组的数,尝试将其放入已有的每一组中。放入前检查该组内的数是否与它互质,如果都互质则可以放入;若与组内任意数不互质,则不能放入该组。若遍历完所有已有组都无法放入,则创建一个新组并将该数放入。通过递归的方式,对下一个未分组的数进行同样的操作。

终止条件:当所有数都成功分组时,记录此时的分组数。在搜索过程中,记录遇到的最小分组数,遍历完所有可能的分组情况后,得到的最小分组数就是答案。

伪代码;

;