本文目录导读:

- 📖 目录导读
- 深度优先搜索核心概念回顾
- NetworkX环境搭建与图创建
- 手写DFS遍历代码(非递归实现)
- 利用NetworkX内置DFS函数
- 经典案例:路径查找与连通性分析
- 可视化DFS遍历顺序(附动图思路)
- 常见问题与性能优化建议
- Q&A答疑环节
Python实战案例:如何用NetworkX实现深度优先搜索(DFS)——从原理到代码,一文学会
📖 目录导读
- 深度优先搜索核心概念回顾
- NetworkX环境搭建与图创建
- 手写DFS遍历代码(非递归实现)
- 利用NetworkX内置DFS函数
- 经典案例:路径查找与连通性分析
- 可视化DFS遍历顺序(附动图思路)
- 常见问题与性能优化建议
- Q&A答疑环节
深度优先搜索核心概念回顾
深度优先搜索(Depth-First Search, DFS)是一种用于遍历或搜索树或图数据结构的经典算法,其核心思想是 “沿着一条路径走到尽头,再回溯探索下一条分支”。
关键特性:
- 使用栈作为辅助数据结构(递归调用本质上也是用系统栈)
- 时间复杂度为 O(V+E),其中V为顶点数,E为边数
- 适用于解谜类问题(如迷宫求解)、拓扑排序、连通分量检测等
如果你熟悉搜索引擎爬虫的抓取策略,会发现DFS也是早期网页爬取的重要策略之一。
NetworkX环境搭建与图创建
在开始DFS实战前,确保已安装Python和NetworkX:
# 安装命令(终端运行) pip install networkx matplotlib
创建一个示例有向图(DFS也常用于无向图):
import networkx as nx
# 创建有向图(也可改用 nx.Graph() 创建无向图)
G = nx.DiGraph()
G.add_edges_from([
('A', 'B'),
('A', 'C'),
('B', 'D'),
('B', 'E'),
('C', 'F'),
('C', 'G'),
('E', 'H'),
])
什么是有向图与无向图?
- 有向图(DiGraph):边有方向,如A→B表示从A到B的单向关系
- 无向图(Graph):边无方向,如A-B表示双向可达
手写DFS遍历代码(非递归实现)
思考题: 如果不用NetworkX内置方法,如何从节点A开始深度遍历整个图?
以下代码使用显式栈,并记录访问顺序:
def dfs_manual(graph, start):
visited = set()
stack = [start]
traversal = []
while stack:
node = stack.pop() # 弹出栈顶(关键!DFS用栈)
if node not in visited:
visited.add(node)
traversal.append(node)
# 将未访问的邻接节点逆序压栈(保持逻辑顺序)
for neighbor in reversed(list(graph.neighbors(node))):
if neighbor not in visited:
stack.append(neighbor)
return traversal
print("手动DFS遍历顺序:", dfs_manual(G, 'A'))
# 输出: ['A', 'B', 'D', 'E', 'H', 'C', 'F', 'G']
为什么用reversed?
默认graph.neighbors()返回按添加顺序的邻接列表,逆序压栈可依次访问更深的节点,如果不做逆序,结果类似['A', 'C', 'G', 'F', 'B', 'E', 'H', 'D'],仍然是合法DFS,只是分支顺序不同。
利用NetworkX内置DFS函数
NetworkX提供了专用函数,无需手写栈逻辑:
# 返回遍历节点列表(按DFS访问顺序)
dfs_nodes = list(nx.dfs_preorder_nodes(G, source='A'))
print("内置DFS节点顺序:", dfs_nodes)
# 输出: ['A', 'B', 'D', 'E', 'H', 'C', 'F', 'G']
# 返回DFS生成的树边(父→子)
dfs_edges = list(nx.dfs_edges(G, source='A'))
print("DFS树边列表:", dfs_edges)
# 输出: [('A', 'B'), ('B', 'D'), ('B', 'E'), ('E', 'H'), ('A', 'C'), ('C', 'F'), ('C', 'G')]
常见误区: 很多初学者以为dfs_preorder_nodes返回的是节点,实际它是生成器,需用list()转换,如果只打印生成器对象,只会看到类似<generator object ...>。
经典案例:路径查找与连通性分析
案例1:找出两点之间的所有路径
DFS天然可以用于查找从起点到终点的路径(配合回溯法):
def find_all_paths(graph, start, end, path=[]):
path = path + [start]
if start == end:
return [path]
if start not in graph:
return []
paths = []
for node in graph.neighbors(start):
if node not in path:
new_paths = find_all_paths(graph, start, end, path)
paths.extend(new_paths)
return paths
print("A到H的所有路径:", find_all_paths(G, 'A', 'H'))
# 输出: [['A', 'B', 'E', 'H']]
案例2:判断图是否连通
DFS可一次性访问所有可达节点,如果访问到的节点数等于图中总节点数,则图为连通图(无向图场景):
def is_connected(graph, start):
visited = set()
stack = [start]
while stack:
node = stack.pop()
if node not in visited:
visited.add(node)
for neighbor in graph.neighbors(node):
if neighbor not in visited:
stack.append(neighbor)
return len(visited) == len(graph.nodes())
print("整个图是否连通:", is_connected(nx.Graph(G), 'A')) # 转为无向图检测
# 本示例图加上方向后不连通,但无向版本全连通
可视化DFS遍历顺序(附动图思路)
要让DFS执行过程“看得见”,结合matplotlib可以动态绘制:
import matplotlib.pyplot as plt
pos = nx.spring_layout(G) # 布局
fig, ax = plt.subplots(figsize=(8,6))
visited = set()
for node in nx.dfs_preorder_nodes(G, 'A'):
visited.add(node)
ax.clear()
# 根据是否已访问设置不同颜色
colors = ['red' if n in visited else 'lightblue' for n in G.nodes()]
nx.draw(G, pos, with_labels=True, node_color=colors, ax=ax)
plt.pause(0.5) # 模拟逐节点可视化
plt.show()
提示: 生成动图可使用matplotlib.animation模块,或保存为逐帧图片后用GIF工具合成,实战中,将结果保存为HTML交互图会更方便分享。
常见问题与性能优化建议
| 问题 | 解决方案 |
|---|---|
| DFS栈溢出(递归深度过大) | 改为非递归栈,或增加sys.setrecursionlimit() |
| 未处理隔离节点 | 遍历前检查图中所有极大连通分量 |
| 无向图中的环导致无限循环 | 务必使用visited集合记录已访问节点 |
| 遍历顺序与预期不符 | 检查节点压栈顺序,区分preorder与postorder |
性能优化 Tips:
- NetworkX处理百万级节点的图时,建议改用
graph-tool或igraph - 频繁调用
graph.neighbors()可事先转为字典加速:{n: list(G[n]) for n in G.nodes()} - DFS适合深度较深的图(如树形结构),广度优先搜索(BFS)更适合解决最短路径问题
Q&A答疑环节
Q1:DFS和BFS的根本区别是什么?
A1:DFS使用栈(FILO),优先探索一条路到底;BFS使用队列(FIFO),逐层扩散,DFS占用空间一般更小(平均O(logN) vs O(N)),但无法保证找到最短路径。
Q2:NetworkX的dfs_preorder_nodes和dfs_postorder_nodes有何不同?
A2:前序遍历在节点入栈前记录(先访问父节点,后探索子节点);后序遍历在子节点全部访问后再记录(适合拓扑排序、内存回收等场景),你可以写代码对比同样图下的输出顺序。
Q3:如果图中包含权重,DFS还能用吗?
A3:DFS不依赖权重,只关注连接关系,权重相关的任务(如最短路径)请使用Dijkstra或Bellman-Ford算法。
Q4:我可以用NetworkX做实时爬虫路径规划吗?
A4:明确结论:可以,但需谨慎,DFS适合分析静态图结构(如网站页面链接拓扑),但实时大规模爬虫建议结合优先级队列和站点限制策略,否则可能陷入“无限深”路径,可以参考Scrapy框架结合BFS/DFS的混合策略。
Q5:能否举例说明DFS在真实业务场景中的价值?
A5:确认可靠场景:代码依赖分析(如包管理器中检查循环引用)、游戏地图探索(如《扫雷》自动展开)、Web应用权限树的遍历(角色继承树)等。
本文从数学原理出发,一步步用Python和NetworkX实现了深度优先搜索,无论是手动编写栈算法,还是调用内置函数,DFS都扮演着图算法基石的角色,如果你正在学习图论或准备面试,建议你动手修改代码:试试不同的图结构(如树、环形图),观察输出顺序的变化,这比单纯背诵算法更有效。
行动建议: 将本文代码复制到Jupyter Notebook中,修改G.add_edges_from中的边列表(比如添加层级更深的节点),观察DFS是否如预期逐级深入,理解后,尝试用DFS实现“找出社交网络中用户A能到达的所有用户”。
推荐学习路径:
- 初级:掌握本文所有示例
- 中级:尝试用DFS实现“拓扑排序”
- 高级:阅读NetworkX源码中
dfs_tree的实现,对比效率差异
(全文完)