深度优先搜索(DFS)理论知识基础
总的来说,深度优先搜索(DFS)是一种用于遍历或搜索图(包括树)的算法,与广度优先搜索不同,它沿着一个方向深入探索直到无法继续再回溯换方向。其搜索过程包括选择起始顶点、深入探索路径和回溯,代码实现有递归和非递归(利用栈)两种方式,递归实现需确定函数参数、设置终止条件和处理当前节点与递归探索相邻节点,非递归则通过栈模拟递归过程。它可用于图的遍历、路径搜索、生成树和森林构建、有向无环图的拓扑排序以及回溯问题等多种场景。
一、定义与概念
深度优先搜索是一种用于遍历或搜索图(包括树,树是一种特殊的图)的算法。它从起始顶点开始,沿着一条路径尽可能深地探索图,直到无法继续(即到达叶子节点或者遇到已经访问过的节点),然后回溯到上一个未完全探索的顶点,继续探索其他路径。
二、与广度优先搜索(BFS)的对比
- 深度优先搜索(DFS):侧重于沿着一个方向深入探索,就像走迷宫时一直沿着一条通道走,直到走不通了才回头换另一个方向。这种搜索方式在搜索过程中可能会深入到图的较远层次,然后再回溯到较浅层次探索其他分支。
- 广度优先搜索(BFS):是从起始顶点开始,先访问它的所有邻接顶点,然后再依次访问这些邻接顶点的邻接顶点,一层一层地向外扩展,类似水波从中心向外扩散的过程。
三、搜索过程
- 选择起始顶点:首先确定一个起始顶点作为搜索的起点。
- 深入探索路径:从起始顶点开始,选择一个与之相邻且未被访问过的顶点,然后递归地对这个新顶点进行相同的操作,即继续选择它的相邻未访问顶点进行探索,一直这样深入下去。
- 回溯:当遇到一个顶点,其所有相邻顶点都已被访问过(即走到尽头),就回溯到上一个有未访问邻接顶点的顶点。回溯的过程就是撤销之前的路径选择,然后尝试其他可能的路径。这个过程会不断重复,直到所有可达顶点都被访问过。
四、代码框架
- 递归实现
- 函数定义与参数:
- 通常定义一个函数
dfs(参数),参数的选择取决于具体的问题。一般需要传入图的结构表示和当前搜索的节点信息。例如,可以有vector<vector<int>> graph表示图(如果是邻接矩阵或邻接表形式)和int curNode表示当前节点。 - 还可能需要一些变量来保存搜索结果,如
vector<vector<int>> result用于保存符合条件的所有路径,vector<int> path用于保存从起点到当前节点的路径。这些变量可以是全局变量,也可以作为函数参数传递,具体取决于个人习惯和代码结构。
- 通常定义一个函数
- 函数定义与参数:
以下是用 Python 实现一个简单的递归 DFS,假设图以邻接表形式表示:
graph = {
1: [2, 3],
2: [4],
3: [5],
4: [],
5: []
}
def dfs(node, visited=set()):
if node in visited:
return
visited.add(node)
print(node)
for neighbor in graph[node]:
dfs(neighbor, visited)
- 终止条件:
- 终止条件用于判断递归何时停止。例如,当找到目标节点、遍历完所有可达节点或者满足某种特定条件时终止。常见的写法是
if (终止条件) {存放结果; return;}。终止条件的正确设置很关键,设置不当可能导致死循环或栈溢出等问题。有些情况下,终止条件也可能隐含在递归调用的逻辑中,即不符合继续递归的条件时就不会向下递归。
- 终止条件用于判断递归何时停止。例如,当找到目标节点、遍历完所有可达节点或者满足某种特定条件时终止。常见的写法是
以下是一个带终止条件的示例,比如找到目标节点 5 就停止:
graph = {
1: [2, 3],
2: [4],
3: [5],
4: [],
5: []
}
def dfs(node, target, visited=set()):
if node == target:
return True
if node in visited:
return False
visited.add(node)
for neighbor in graph[node]:
if dfs(neighbor, target, visited):
return True
return False
- 处理当前节点并递归探索相邻节点:
- 一般通过一个
for循环来遍历当前节点所能到达的所有相邻节点。在循环内部,首先处理当前选择的相邻节点(例如,将其加入路径path),然后进行递归调用dfs(图,选择的节点),继续深入探索。在递归调用之后,需要进行回溯操作,撤销对当前节点的处理结果(例如,将其从路径path中移除),以便尝试其他相邻节点。
- 一般通过一个
以下是一个在遍历过程中记录路径的示例:
graph = {
1: [2, 3],
2: [4],
3: [5],
4: [],
5: []
}
result = []
path = []
def dfs(node):
if node in path:
return
path.append(node)
if len(path) > len(result) and node == 5:
result[:] = path.copy()
for neighbor in graph[node]:
dfs(neighbor)
path.pop()
- 非递归实现(使用栈)
- 除了递归实现外,深度优先搜索也可以使用栈来实现。首先将起始顶点压入栈中,然后进入循环。在每次循环中,弹出栈顶元素,处理该元素(例如,标记为已访问),然后将其未访问的相邻顶点压入栈中。重复这个过程,直到栈为空。这种实现方式模拟了递归调用栈的过程,在某些场景下可以避免递归调用栈溢出的问题。
以下是用栈实现 DFS 的示例:
graph = {
1: [2, 3],
2: [4],
3: [5],
4: [],
5: []
}
stack = []
visited = set()
def dfs(start):
stack.append(start)
while stack:
node = stack.pop()
if node not in visited:
visited.add(node)
print(node)
for neighbor in reversed(graph[node]):
stack.append(neighbor)
五、应用场景
- 图的遍历:用于确定图是否连通,计算图的连通分量数量,遍历图中的所有节点等。
以下是判断图是否连通的示例:
graph = {
1: [2, 3],
2: [1, 4],
3: [1, 5],
4: [2],
5: [3]
}
def is_connected(graph):
visited = set()
start = list(graph.keys())[0]
dfs(start, visited)
return len(visited) == len(graph)
def dfs(node, visited):
if node in visited:
return
visited.add(node)
for neighbor in graph[node]:
dfs(neighbor, visited)
- 路径搜索:在图中寻找从一个顶点到另一个顶点的所有可能路径,例如在迷宫问题、网络路径搜索等场景中。
以下是在图中寻找从一个顶点到另一个顶点路径的示例:
graph = {
1: [2, 3],
2: [4],
3: [5],
4: [],
5: []
}
def find_path(start, end):
path = []
visited = set()
def dfs(node):
if node == end:
path.append(node)
return True
if node in visited:
return False
visited.add(node)
for neighbor in graph[node]:
if dfs(neighbor):
path.append(node)
return True
return False
dfs(start)
return path[::-1] if path else None
- 生成树和森林:可以用于构建图的生成树或森林,特别是在深度优先生成树的生成过程中。
以下是构建深度优先生成树的示例:
graph = {
1: [2, 3],
2: [4],
3: [5],
4: [],
5: []
}
tree = {}
visited = set()
def build_dfs_tree(start):
if start in visited:
return
visited.add(start)
for neighbor in graph[start]:
if neighbor not in visited:
if start not in tree:
tree[start] = []
tree[start].append(neighbor)
build_dfs_tree(neighbor)
- 拓扑排序(对于有向无环图):结合适当的标记和处理,可以用于有向无环图的拓扑排序。在拓扑排序过程中,通过深度优先搜索可以确定节点之间的先后顺序。
以下是用 DFS 实现拓扑排序的示例:
graph = {
1: [2, 3],
2: [4],
3: [4],
4: []
}
result = []
visited = set()
def topological_sort(node):
if node in visited:
return
visited.add(node)
for neighbor in graph[node]:
topological_sort(neighbor)
result.append(node)
for node in graph:
topological_sort(node)
result.reverse()
print(result)
- 解决回溯问题:很多回溯算法问题本质上是深度优先搜索的应用,如组合问题、排列问题、子集问题等。在这些问题中,深度优先搜索用于遍历所有可能的解空间,通过回溯来撤销不合适的选择,从而找到满足条件的所有解。
以下是解决组合问题的示例:
nums = [1, 2, 3]
target = 3
result = []
def combination_sum(index, path, target):
if target == 0:
result.append(path.copy())
return
if target < 0 or index >= len(nums):
return
for i in range(index, len(nums)):
path.append(nums[i])
combination_sum(i, path, target - nums[i])
path.pop()
combination_sum(0, [], target)
print(result)
175

被折叠的 条评论
为什么被折叠?



