深度优先搜索算法
概述
深度优先搜索(Depth-First Search, DFS)是一种用于遍历或搜索树和图的算法。该算法从指定的起始节点开始,沿着一条路径尽可能深地搜索,直到无法继续前进时才回溯,然后选择另一条路径继续搜索。DFS是由Charles Pierre Trémaux在19世纪末提出的,后来在计算机科学中被广泛应用。
算法描述
DFS使用栈数据结构来实现深度优先搜索:
- 初始化:将起始节点压入栈,并标记为已访问
- 循环处理:当栈不为空时:
- 从栈中弹出一个节点
- 处理该节点(如记录路径、检查是否为目标等)
- 将该节点的所有未访问邻居压入栈,并标记为已访问
步骤详解
- 创建一个栈和一个已访问集合
- 将起始节点压入栈,并标记为已访问
- 当栈不为空:
- 从栈顶弹出一个节点
current - 处理
current节点 - 对于
current的每个邻居neighbor:- 如果
neighbor未被访问:- 将
neighbor标记为已访问 - 将
neighbor压入栈
- 将
- 如果
- 从栈顶弹出一个节点
- 当栈为空时,搜索结束
数学基础
时间复杂度
- 时间复杂度:O(V+E)O(V + E)O(V+E) - 其中 VVV 是顶点数量,EEE 是边数量
- 每个顶点和边最多被访问一次
空间复杂度
- 空间复杂度:O(V)O(V)O(V) - 最坏情况下需要存储所有顶点
- 递归实现的DFS空间复杂度为 O(h)O(h)O(h),其中 hhh 是树的高度
数学分析
DFS的时间复杂度分析:
- 访问所有顶点:O(V)O(V)O(V)
- 遍历所有边:O(E)O(E)O(E)
- 总时间复杂度:O(V+E)O(V + E)O(V+E)
DFS的空间复杂度分析:
- 栈最坏情况下存储所有顶点:O(V)O(V)O(V)
- 已访问集合存储所有顶点:O(V)O(V)O(V)
- 总空间复杂度:O(V)O(V)O(V)
对于递归实现的DFS,空间复杂度取决于递归深度,即树的高度 hhh,因此为 O(h)O(h)O(h)。
实现
Python实现
def dfs(graph, start):
"""
深度优先搜索实现
参数:
graph: 图的邻接表表示
start: 起始节点
返回:
访问顺序列表
"""
visited = set()
stack = [start]
visited.add(start)
result = []
while stack:
current = stack.pop()
result.append(current)
# 注意:这里需要反转邻居的顺序,以保证与递归DFS相同的访问顺序
for neighbor in reversed(graph[current]):
if neighbor not in visited:
visited.add(neighbor)
stack.append(neighbor)
return result
def dfs_recursive(graph, start, visited=None):
"""
递归实现的深度优先搜索
参数:
graph: 图的邻接表表示
start: 起始节点
visited: 已访问节点集合
返回:
访问顺序列表
"""
if visited is None:
visited = set()
visited.add(start)
result = [start]
for neighbor in graph[start]:
if neighbor not in visited:
result.extend(dfs_recursive(graph, neighbor, visited))
return result
def dfs_path(graph, start, end):
"""
使用DFS查找从start到end的路径
参数:
graph: 图的邻接表表示
start: 起始节点
end: 目标节点
返回:
从start到end的路径(如果存在)
"""
stack = [(start, [start])]
visited = set([start])
while stack:
current, path = stack.pop()
if current == end:
return path
for neighbor in graph[current]:
if neighbor not in visited:
visited.add(neighbor)
stack.append((neighbor, path + [neighbor]))
return None
def dfs_all_paths(graph, start, end):
"""
使用DFS查找从start到end的所有路径
参数:
graph: 图的邻接表表示
start: 起始节点
end: 目标节点
返回:
从start到end的所有路径列表
"""
stack = [(start, [start])]
all_paths = []
while stack:
current, path = stack.pop()
if current == end:
all_paths.append(path)
continue
for neighbor in graph[current]:
if neighbor not in path: # 避免环路
stack.append((neighbor, path + [neighbor]))
return all_paths
# 示例图
example_graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
# 使用示例
print("DFS迭代访问顺序:", dfs(example_graph, 'A'))
print("DFS递归访问顺序:", dfs_recursive(example_graph, 'A'))
path = dfs_path(example_graph, 'A', 'F')
print(f"从A到F的路径: {path}")
all_paths = dfs_all_paths(example_graph, 'A', 'F')
print(f"从A到F的所有路径:")
for i, path in enumerate(all_paths):
print(f"路径{i+1}: {' -> '.join(path)}")
C++实现
#include <iostream>
#include <vector>
#include <stack>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
using namespace std;
// DFS迭代实现
vector<string> dfs(const unordered_map<string, vector<string>>& graph, const string& start) {
unordered_set<string> visited;
stack<string> s;
vector<string> result;
s.push(start);
visited.insert(start);
while (!s.empty()) {
string current = s.top();
s.pop();
result.push_back(current);
// 反转邻居的顺序以保证与递归DFS相同的访问顺序
for (auto it = graph.at(current).rbegin(); it != graph.at(current).rend(); ++it) {
const string& neighbor = *it;
if (visited.find(neighbor) == visited.end()) {
visited.insert(neighbor);
s.push(neighbor);
}
}
}
return result;
}
// DFS递归实现
void dfs_recursive_helper(const unordered_map<string, vector<string>>& graph,
const string& current,
unordered_set<string>& visited,
vector<string>& result) {
visited.insert(current);
result.push_back(current);
for (const string& neighbor : graph.at(current)) {
if (visited.find(neighbor) == visited.end()) {
dfs_recursive_helper(graph, neighbor, visited, result);
}
}
}
vector<string> dfs_recursive(const unordered_map<string, vector<string>>& graph, const string& start) {
unordered_set<string> visited;
vector<string> result;
dfs_recursive_helper(graph, start, visited, result);
return result;
}
// 查找路径
vector<string> dfs_path(const unordered_map<string, vector<string>>& graph,
const string& start, const string& end) {
stack<pair<string, vector<string>>> s;
unordered_set<string> visited;
s.push({start, {start}});
visited.insert(start);
while (!s.empty()) {
auto current = s.top();
s.pop();
string current_node = current.first;
vector<string> path = current.second;
if (current_node == end) {
return path;
}
for (const string& neighbor : graph.at(current_node)) {
if (visited.find(neighbor) == visited.end()) {
visited.insert(neighbor);
vector<string> new_path = path;
new_path.push_back(neighbor);
s.push({neighbor, new_path});
}
}
}
return {};
}
// 查找所有路径
vector<vector<string>> dfs_all_paths(const unordered_map<string, vector<string>>& graph,
const string& start, const string& end) {
stack<pair<string, vector<string>>> s;
vector<vector<string>> all_paths;
s.push({start, {start}});
while (!s.empty()) {
auto current = s.top();
s.pop();
string current_node = current.first;
vector<string> path = current.second;
if (current_node == end) {
all_paths.push_back(path);
continue;
}
for (const string& neighbor : graph.at(current_node)) {
if (find(path.begin(), path.end(), neighbor) == path.end()) { // 避免环路
vector<string> new_path = path;
new_path.push_back(neighbor);
s.push({neighbor, new_path});
}
}
}
return all_paths;
}
int main() {
// 示例图
unordered_map<string, vector<string>> example_graph = {
{"A", {"B", "C"}},
{"B", {"A", "D", "E"}},
{"C", {"A", "F"}},
{"D", {"B"}},
{"E", {"B", "F"}},
{"F", {"C", "E"}}
};
// 使用示例
cout << "DFS迭代访问顺序: ";
vector<string> dfs_result = dfs(example_graph, "A");
for (size_t i = 0; i < dfs_result.size(); i++) {
if (i > 0) cout << " -> ";
cout << dfs_result[i];
}
cout << endl;
cout << "DFS递归访问顺序: ";
vector<string> dfs_recursive_result = dfs_recursive(example_graph, "A");
for (size_t i = 0; i < dfs_recursive_result.size(); i++) {
if (i > 0) cout << " -> ";
cout << dfs_recursive_result[i];
}
cout << endl;
vector<string> path = dfs_path(example_graph, "A", "F");
cout << "从A到F的路径: ";
for (size_t i = 0; i < path.size(); i++) {
if (i > 0) cout << " -> ";
cout << path[i];
}
cout << endl;
vector<vector<string>> all_paths = dfs_all_paths(example_graph, "A", "F");
cout << "从A到F的所有路径:" << endl;
for (size_t i = 0; i < all_paths.size(); i++) {
cout << "路径" << i+1 << ": ";
for (size_t j = 0; j < all_paths[i].size(); j++) {
if (j > 0) cout << " -> ";
cout << all_paths[i][j];
}
cout << endl;
}
return 0;
}
变体和优化
1. 迭代加深深度优先搜索(IDDFS)
def iddfs(graph, start, max_depth):
"""
迭代加深深度优先搜索
参数:
graph: 图的邻接表表示
start: 起始节点
max_depth: 最大搜索深度
返回:
在指定深度范围内的所有节点
"""
for depth in range(max_depth + 1):
result = []
visited = set()
def dfs_limited(node, current_depth):
if current_depth > depth:
return
visited.add(node)
result.append((node, current_depth))
for neighbor in graph[node]:
if neighbor not in visited:
dfs_limited(neighbor, current_depth + 1)
dfs_limited(start, 0)
print(f"深度 {depth} 的节点数量: {len([n for n, d in result if d == depth])}")
return result
2. 拓扑排序
def topological_sort(graph):
"""
使用DFS进行拓扑排序
参数:
graph: 有向图的邻接表表示
返回:
拓扑排序结果列表
"""
visited = set()
temp_visited = set()
result = []
def dfs_topological(node):
if node in temp_visited:
raise ValueError("图中存在环路,无法进行拓扑排序")
if node in visited:
return
temp_visited.add(node)
for neighbor in graph.get(node, []):
dfs_topological(neighbor)
temp_visited.remove(node)
visited.add(node)
result.append(node)
for node in graph:
if node not in visited:
dfs_topological(node)
return result[::-1] # 反转结果
3. 强连通分量(SCC)查找
def strongly_connected_components(graph):
"""
使用Kosaraju算法查找强连通分量
参数:
graph: 有向图的邻接表表示
返回:
强连通分量列表
"""
# 第一步:完成DFS并记录完成顺序
visited = set()
order = []
def dfs_first(node):
visited.add(node)
for neighbor in graph.get(node, []):
if neighbor not in visited:
dfs_first(neighbor)
order.append(node)
for node in graph:
if node not in visited:
dfs_first(node)
# 第二步:反转图
reversed_graph = {}
for node in graph:
reversed_graph[node] = []
for node in graph:
for neighbor in graph[node]:
reversed_graph[neighbor].append(node)
# 第三步:按完成顺序的逆序进行DFS
visited = set()
sccs = []
def dfs_second(node, component):
visited.add(node)
component.append(node)
for neighbor in reversed_graph.get(node, []):
if neighbor not in visited:
dfs_second(neighbor, component)
for node in reversed(order):
if node not in visited:
component = []
dfs_second(node, component)
sccs.append(component)
return sccs
应用场景
DFS广泛应用于:
- 路径查找:在图中寻找路径
- 拓扑排序:对有向无环图进行排序
- 连通性检测:检测图的连通性
- 环路检测:检测图中是否存在环路
- 迷宫求解:求解迷宫问题
- 决策树遍历:在决策树中搜索最优解
优点和缺点
优点
- 实现简单:算法逻辑清晰,易于实现
- 空间效率相对较高:相比BFS,在某些情况下空间效率更高
- 适合深度优先场景:适合深度较大的搜索场景
- 能够找到所有路径:能够找到从起点到终点的所有可能路径
- 检测环路:能够方便地检测图中是否存在环路
缺点
- 不保证最短路径:在无权图中不能保证找到最短路径
- 可能陷入无限循环:在有环图中如果没有适当的处理可能导致无限循环
- 栈溢出风险:递归实现可能导致栈溢出
- 不适合广度优先场景:对于分支因子大的图效率较低
- 内存密集:需要维护栈和已访问集合
性能比较
| 算法 | 时间复杂度 | 空间复杂度 | 最优性 | 适用场景 |
|---|---|---|---|---|
| DFS | O(V+E)O(V + E)O(V+E) | O(V)O(V)O(V) | 否 | 深度优先搜索 |
| BFS | O(V+E)O(V + E)O(V+E) | O(V)O(V)O(V) | 是(无权图) | 无权图最短路径 |
| Dijkstra | O((V+E)logV)O((V + E) \log V)O((V+E)logV) | O(V+E)O(V + E)O(V+E) | 是 | 加权图最短路径 |
| A* | O(bd)O(b^d)O(bd) | O(bd)O(b^d)O(bd) | 是(可容性) | 启发式搜索 |
| IDDFS | O(bd)O(b^d)O(bd) | O(d)O(d)O(d) | 是 | 深度受限搜索 |
实际应用示例
示例1:迷宫求解
def maze_dfs_example():
"""
迷宫求解示例
"""
# 迷宫:0表示可通过,1表示墙壁
maze = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 1, 1, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 1, 0, 1, 1, 1, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0]
]
# 将迷宫转换为图的邻接表表示
def maze_to_graph(maze):
rows = len(maze)
cols = len(maze[0])
graph = {}
for i in range(rows):
for j in range(cols):
if maze[i][j] == 0: # 可通过的位置
node = f"{i},{j}"
graph[node] = []
# 检查四个方向
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for dx, dy in directions:
ni, nj = i + dx, j + dy
if 0 <= ni < rows and 0 <= nj < cols and maze[ni][nj] == 0:
neighbor = f"{ni},{nj}"
graph[node].append(neighbor)
return graph
maze_graph = maze_to_graph(maze)
# 起点和终点
start = "0,0"
end = "9,9"
# 使用DFS寻找路径
path = dfs_path(maze_graph, start, end)
if path:
print(f"从 {start} 到 {end} 的路径:")
for i, pos in enumerate(path):
if i > 0:
print(" -> ", end="")
print(pos, end="")
print(f"\n路径长度: {len(path)}")
# 在迷宫中显示路径
print("\n迷宫路径显示 (P表示路径):")
path_set = set(path)
for i in range(len(maze)):
for j in range(len(maze[0])):
pos = f"{i},{j}"
if pos == start:
print("S", end=" ")
elif pos == end:
print("E", end=" ")
elif pos in path_set:
print("P", end=" ")
elif maze[i][j] == 1:
print("#", end=" ")
else:
print(".", end=" ")
print()
else:
print(f"从 {start} 到 {end} 没有可行路径")
maze_dfs_example()
示例2:依赖关系解析
def dependency_resolution_example():
"""
依赖关系解析示例
"""
# 项目依赖图:包名到依赖包列表的映射
dependencies = {
'A': ['B', 'C'],
'B': ['D'],
'C': ['D', 'E'],
'D': [],
'E': ['F'],
'F': []
}
# 使用DFS进行拓扑排序
try:
build_order = topological_sort(dependencies)
print("项目构建顺序:")
for i, package in enumerate(build_order):
print(f"步骤 {i+1}: 构建 {package}")
except ValueError as e:
print(f"错误: {e}")
# 查找所有可能的构建顺序
print("\n所有可能的构建顺序:")
all_orders = []
def find_all_orders(graph, current_order, remaining):
if not remaining:
all_orders.append(current_order.copy())
return
for package in remaining:
# 检查是否所有依赖都已构建
can_build = True
for dep in graph.get(package, []):
if dep not in current_order:
can_build = False
break
if can_build:
current_order.append(package)
new_remaining = [p for p in remaining if p != package]
find_all_orders(graph, current_order, new_remaining)
current_order.pop()
find_all_orders(dependencies, [], list(dependencies.keys()))
for i, order in enumerate(all_orders):
print(f"顺序 {i+1}: {' -> '.join(order)}")
dependency_resolution_example()
示例3:社交网络分析
def social_network_analysis_example():
"""
社交网络分析示例
"""
# 社交网络图:人名到朋友列表的映射
social_network = {
'Alice': ['Bob', 'Charlie'],
'Bob': ['Alice', 'David', 'Eve'],
'Charlie': ['Alice', 'Frank'],
'David': ['Bob'],
'Eve': ['Bob', 'Frank'],
'Frank': ['Charlie', 'Eve']
}
# 查找Alice的所有朋友(深度为1)
print("Alice的直接朋友:")
alice_friends = social_network['Alice']
for friend in alice_friends:
print(f"- {friend}")
# 查找Alice的朋友的朋友(深度为2)
print("\nAlice的朋友的朋友:")
friends_of_friends = set()
for friend in alice_friends:
for fof in social_network.get(friend, []):
if fof != 'Alice' and fof not in alice_friends:
friends_of_friends.add(fof)
for fof in friends_of_friends:
print(f"- {fof}")
# 查找从Alice到David的所有路径
print("\n从Alice到David的所有社交路径:")
all_paths = dfs_all_paths(social_network, 'Alice', 'David')
for i, path in enumerate(all_paths):
print(f"路径{i+1}: {' -> '.join(path)}")
# 检测社交网络中的环路
print("\n检测社交网络中的环路:")
sccs = strongly_connected_components(social_network)
for i, scc in enumerate(sccs):
if len(scc) > 1:
print(f"环路{i+1}: {' <-> '.join(scc)}")
social_network_analysis_example()
结论
深度优先搜索是图论中最基础和最重要的算法之一。它通过深度优先的方式遍历图,能够找到从起点到终点的所有可能路径,并且能够方便地检测图中是否存在环路。DFS的简单性和灵活性使其成为许多图算法的基础。
从迷宫求解到依赖关系解析,从社交网络分析到编译器设计,DFS的身影无处不在。虽然DFS在某些场景下可能不如其他算法高效,但其能够找到所有路径和检测环路的能力使其成为计算机科学教育中的经典算法。
DFS的思想不仅限于图遍历,还广泛应用于各种需要深度优先处理的算法设计中。其栈数据结构的使用也为许多其他算法提供了灵感。
&spm=1001.2101.3001.5002&articleId=165489198&d=1&t=3&u=b435a540c9fb4bb59147d28ad2ec940e)
522

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



