📅  最后修改于: 2023-12-03 14:56:06.433000             🧑  作者: Mango
深度优先搜索(Depth First Search,简称DFS)是一种用于遍历或搜索树或图的算法。DFS 算法会从起点开始,遍历整个图,直到找到所需的节点或遍历完整个图。
在面试中,DFS 是一个经常被问到的话题。本文将介绍深度优先搜索的 10 大面试问题,并提供相应的解答。
深度优先搜索是一种常见的遍历或搜索树或图的算法。该算法会从起点开始,依次遍历相邻的节点,直到找到所需节点或遍历完整个图。
DFS 算法有两种实现方式:递归实现和非递归实现。递归实现是最常用的实现方式,非递归实现则需要借助栈来实现。
DFS 算法通常使用递归实现。以下代码是递归实现 DFS 的示例:
def dfs(node, visited):
if not node:
return
visited.add(node)
for neighbor in node.neighbors:
if neighbor not in visited:
dfs(neighbor, visited)
上述代码中,dfs
函数接收一个节点和一个访问过的节点集合。首先,将当前节点标记为已访问,接着遍历所有相邻节点,如果某个相邻节点尚未被访问,则递归访问该节点。最后,遍历完所有相邻节点后,返回上一级调用。
回溯是指在深度优先搜索过程中,当搜索到死胡同时,需要返回上一级节点并且继续遍历其他节点,这一过程就称为回溯。
在实现DFS时,当搜索到死胡同时,需要返回上一级节点并继续搜索其他节点。通常,可以通过递归和栈两种方式来实现回溯。
以下是递归实现回溯的示例代码:
def dfs(node, target, visited, path, res):
if not node:
return
visited.add(node)
path.append(node)
if node == target:
res.append(list(path))
for neighbor in node.neighbors:
if neighbor not in visited:
dfs(neighbor, target, visited, path, res)
path.pop()
visited.remove(node)
上述代码中,每次搜索完所有相邻节点并返回上一级节点时,需要将已访问的节点标记为未访问,并将该节点从路径中删除,以便继续遍历其他节点。
在 DFS 中,如果两个节点之间存在一条路径,则这两个节点相连。
以下代码是用 DFS 判断两个节点是否相连的示例:
def is_connected(node1, node2):
visited = set()
dfs(node1, visited)
return node2 in visited
上述代码中,is_connected
函数接收两个节点 node1
和 node2
,通过 DFS 遍历 node1
所在的连通块,并使用集合 visited
记录已访问的节点。最终,判断节点 node2
是否在已访问的节点集合中即可。
在 DFS 算法中,可以通过记录遍历路径的方式找到从起点到终点的所有路径。
以下是寻找从起点到终点的所有路径的示例代码:
def find_all_paths(start, end, path, res):
if start == end:
res.append(list(path))
return
path.append(start)
for node in start.neighbors:
if node not in path:
find_all_paths(node, end, path, res)
path.pop()
上述代码中,find_all_paths
函数接收一个起点和一个终点,并使用 DFS 遍历所有可能的路径。在搜索到终点时,将已访问的路径存储在结果集 res
中。
要寻找从起点到终点的最短路径,可以使用 BFS(广度优先搜索)算法。BFS 算法会按层遍历图,因此找到的第一个路径就是最短路径。
以下是寻找从起点到终点的最短路径的示例代码:
def find_shortest_path(start, end):
queue = collections.deque([(start, [start])])
visited = set()
while queue:
node, path = queue.popleft()
if node == end:
return path
visited.add(node)
for neighbor in node.neighbors:
if neighbor not in visited:
queue.append((neighbor, path + [neighbor]))
return None
上述代码中,find_shortest_path
函数使用 BFS 算法遍历图,直到找到终点。在每个节点上,将遍历路径存储在队列中,并用集合 visited
记录已访问的节点。
有向图中是否存在环可以使用拓扑排序算法来判断。在拓扑排序时,如果存在环,则拓扑排序无法完成。
以下是使用 DFS 实现拓扑排序的示例代码:
def topo_sort(node, visited, stack):
visited.add(node)
for neighbor in node.neighbors:
if neighbor not in visited:
topo_sort(neighbor, visited, stack)
stack.append(node)
def has_cycle_dfs(nodes):
visited = set()
stack = []
for node in nodes:
if node not in visited:
topo_sort(node, visited, stack)
return len(stack) != len(nodes)
上述代码中,topo_sort
函数使用 DFS 遍历所有节点,并将访问过的节点存储在逆拓扑排序栈中。最后,如果拓扑排序栈和节点集合的长度不同,则说明存在环。
强连通分量是指有向图中的一组节点,它们相互可达且不与其他节点可达。可以使用 Kosaraju 算法来寻找一张图的强连通分量。
以下是使用 DFS 实现 Kosaraju 算法的示例代码:
def kosaraju(nodes):
# 第一轮 DFS,得到逆拓扑排序栈
visited = set()
stack = []
for node in nodes:
if node not in visited:
dfs(node, visited, stack)
# 第二轮 DFS,得到强连通分量
visited.clear()
result = []
while stack:
node = stack.pop()
if node not in visited:
comp = set()
dfs_reverse(node, visited, comp)
result.append(comp)
return result
def dfs(node, visited, stack):
visited.add(node)
for neighbor in node.neighbors:
if neighbor not in visited:
dfs(neighbor, visited, stack)
stack.append(node)
def dfs_reverse(node, visited, comp):
visited.add(node)
comp.add(node)
for neighbor in node.reverse_neighbors:
if neighbor not in visited:
dfs_reverse(neighbor, visited, comp)
上述代码中,kosaraju
函数使用 DFS 进行两次遍历。第一次遍历得到图的逆拓扑排序栈,第二次遍历得到强连通分量。
割点是指从图中移除后,会导致图不连通的节点。可以使用 DFS 来寻找无向图中的割点。
以下是寻找无向图中割点的示例代码:
def find_cut_points(node, visited, pre, low, time, res):
visited.add(node)
children = 0
is_cut_point = False
pre[node] = low[node] = time[0]
time[0] += 1
for neighbor in node.neighbors:
if neighbor not in visited:
children += 1
find_cut_points(neighbor, visited, pre, low, time, res)
low[node] = min(low[node], low[neighbor])
if pre[node] <= low[neighbor]:
is_cut_point = True
elif neighbor != pre[node]:
low[node] = min(low[node], pre[neighbor])
if (pre[node] == 0 and children > 1) or (pre[node] != 0 and is_cut_point):
res.append(node)
def find_cut_points_in_undirected_graph(nodes):
visited = set()
pre = {}
low = {}
time = [0]
res = []
for node in nodes:
if node not in visited:
find_cut_points(node, visited, pre, low, time, res)
return res
上述代码中,find_cut_points
函数使用 DFS 遍历每个节点,并计算每个节点的 pre
和 low
值。如果某个节点的 low
值大于等于其相邻节点的 pre
值,则该节点是割点。
二叉树的最大深度是指从根节点到最远叶子节点的最长路径。可以使用 DFS 算法来寻找二叉树的最大深度。
以下是查找二叉树最大深度的示例代码:
def max_depth(node):
if not node:
return 0
return max(max_depth(node.left), max_depth(node.right)) + 1
上述代码中,max_depth
函数使用递归的方式遍历二叉树,并计算左右子树的深度,最后返回左右子树深度的较大值加1。