趋近智
图是一种表示关系数据的强大方式。系统地探索这些图表示是各种应用中的常见任务。例如,发现从起始点可达的所有实体、找到两个实体之间的最短连接或理解图中组件的结构,都是重要的目标。图遍历算法提供了有序访问节点的机制来实现这些目标。广度优先搜索(BFS)是一种基本的遍历算法。
想象一下将一颗小石子投入平静的池塘。涟漪会均匀地呈同心圆状扩散。BFS以类似的方式访问图:它从一个选定的源节点开始,首先访问其邻近节点,然后再移动到下一层邻近节点。它逐层访问图。
BFS通过维护一个待访问节点队列和一个已访问节点集合来工作,以避免循环和重复工作。
其工作方式通常如下:
s 来开始遍历。s 入队。s 标记为已访问。u。u(例如,打印其值,检查它是否是目标节点)。u 的每个邻近节点 v:
v 尚未被访问:
v 标记为已访问。v 入队。这个过程确保节点按照它们与源节点距离(边数)的递增顺序被访问。这个特性使得BFS适用于查找无权重图中的最短路径。
我们来追踪在简单图上进行BFS的过程,从节点 'A' 开始。我们将使用邻接表表示:A: [B, D], B: [A, C], C: [B, E], D: [A, E], E: [C, D]。
一个简单的无向图。节点 'F' 被包含进来是为了说明,如果从 'A' 开始,它将不会被访问。
步骤(从A开始):
queue = [A], visited = {A}。queue = [B], visited = {A, B}。queue = [B, D], visited = {A, B, D}。queue = [D, C], visited = {A, B, D, C}。queue = [C, E], visited = {A, B, D, C, E}。queue = [E], visited = {A, B, D, C, E}。queue = [], visited = {A, B, D, C, E}。节点访问(处理)的顺序是A、B、D、C、E。请注意,所有与A距离为1的节点(B、D)都在与A距离为2的节点(C、E)之前被访问。节点F从未被访问,因为它无法从A到达。
以下是更正式的伪代码表示:
BFS(graph, start_node):
设 Q 为一个队列
设 visited 为一个集合
将 start_node 加入 Q
将 start_node 加入 visited
当 Q 不为空时:
current_node = Q.出队()
// 处理 current_node(例如,打印、检查条件)
process(current_node)
对于 graph.get_neighbors(current_node) 中的每个 neighbor:
如果 neighbor 不在 visited 中:
将 neighbor 加入 visited
将 neighbor 加入 Q
使用Python的 collections.deque 进行高效队列实现和字典作为邻接表是常见的做法:
from collections import deque
def bfs(graph, start_node):
"""
在图上执行广度优先搜索。
参数:
graph (dict): 图的邻接表表示
(例如,{'A': ['B', 'D'], ...})。
start_node: 开始遍历的节点。
返回:
list: 节点被访问的顺序列表。
如果 start_node 不在图中,返回空列表。
"""
if start_node not in graph:
return [] # 起始节点必须在图中
visited = set()
queue = deque([start_node])
visited.add(start_node)
visited_order = []
while queue:
current_node = queue.popleft() # 从左侧出队
visited_order.append(current_node)
# 如果需要,在这里处理节点(例如,print(current_node))
# 将邻近节点加入队列
# 使用 graph.get(current_node, []) 处理没有出边的节点
for neighbor in graph.get(current_node, []):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor) # 从右侧入队
return visited_order
# 使用可视化中的图进行示例:
graph_example = {
'A': ['B', 'D'],
'B': ['A', 'C'],
'C': ['B', 'E'],
'D': ['A', 'E'],
'E': ['C', 'D'],
'F': [] # 节点 F 未连接到主组件
}
start = 'A'
traversal_path = bfs(graph_example, start)
print(f"BFS traversal starting from {start}: {traversal_path}")
# 预期输出: 从A开始的BFS遍历: ['A', 'B', 'D', 'C', 'E']
start_unconnected = 'F'
traversal_unconnected = bfs(graph_example, start_unconnected)
print(f"BFS traversal starting from {start_unconnected}: {traversal_unconnected}")
# 预期输出: 从F开始的BFS遍历: ['F']
了解BFS的性能对于选择合适的算法很重要。
时间复杂度: 在最坏情况下(假设使用邻接表表示),BFS 会且仅会访问每个节点和每条边一次。
u 时,我们遍历其所有邻近节点。在整个执行过程中,邻近节点检查的总数对应于所有已访问节点的度数之和。对于有向图,这是已访问节点的出边数量。对于无向图,每条边 (u,v) 会被考虑两次(一次从 u 考虑,一次从 v 考虑)。无论哪种情况,与边相关的总工作量与正在遍历的连通分量中的边数 E 成比例。空间复杂度: 所需空间主要由 visited 集合和 queue 的存储占用。
visited 集合最多可以存储 V 个节点。queue 可能需要容纳特定层次的所有节点。对于非常密集的图或星形图,这可能接近 O(V) 个节点。虽然BFS不总是直接作为模型核心训练循环(如梯度下降)的一部分,但它在机器学习从业者的工具箱中是处理图相关任务的有用的工具:
BFS提供了系统地、逐层地查看图结构的方法。它在无权重图中找到最短路径的能力以及其可预测的 O(V+E) 性能使其成为分析关系数据图的基础算法。接下来我们将了解深度优先搜索(DFS),它使用不同的策略来遍历图。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造