目录标题
- 图的遍历
- 深度优先遍历 Depth First Search
- 广度优先遍历 Breadth First Search
图的遍历
从图中某一个顶点出发,沿着一些边访遍图中所有的顶点,且使用每个顶点仅被访问一次,这个过程称为图的遍历.Graph Traversal.
- 其中,访问限制标志visited[n],只取0和1值.
- 遍历算法:
- 访问初始顶点,
- 按照某种策略依次访问连通子图中未被访问的顶点,
- 寻找下一个未被访问的顶点,将此顶点作为初始顶点,转上一步骤,
- 重复上述步骤直到所有顶点均被访问为止.
深度优先遍历 Depth First Search
树先序遍历的推广.
-
基本思想:
-
1-先任意选定图中一个顶点v,从顶点v开始访问:再选定v的一个没有被访问过的邻接点w,对顶点w进行深度优先遍历,直到图中与当前顶点邻接的顶点全部被访问为止.
-
2-如果仍然有顶点未被访问,则从未访问的顶点中任选一个,执行前述遍历过程.
-
总言:是尽可能深地探索分支,当遇到末端(叶子节点或无法继续前进的点)时回溯,转而探索其他未被访问的分支.
-
最好操作是这么进行的访问过程是通过栈来控制.
-
栈的进出规则:
- 访问的顶点入栈,继续寻找邻接顶点,找到入栈;
- 直到当前的顶点找不到邻接顶点,当前栈顶出栈;
- 访问当前栈顶的元素是否还存在未被访问的邻接顶点,没有就出栈;
- 有的话就访问当前栈顶元素未被访问的邻接顶点;
- 没有的话,直到栈为空,表明图的遍历完成.
- 示意图
-
代码实现
# 深度优先遍历
class Stack:"""栈数据结构实现"""def __init__(self):self.items = []def is_empty(self):"""判断栈是否为空"""return len(self.items) == 0def push(self, item):"""元素入栈"""self.items.append(item)def pop(self):"""元素出栈并返回"""if not self.is_empty():return self.items.pop()return Nonedef peek(self):"""查看栈顶元素"""if not self.is_empty():return self.items[-1]return Noneclass Vertex:"""顶点类"""def __init__(self, data):self.data = data # 顶点数据self.first_edge = None # 指向第一条邻接边class Edge:"""边类"""def __init__(self, adj_vex):self.adj_vex = adj_vex # 邻接顶点索引self.next_edge = None # 指向下一条边class LinkedGraph:"""邻接表图结构"""def __init__(self, vertexs, edges):self.vertex_num = len(vertexs)self.edge_num = len(edges)self.vertex_list = [None] * self.vertex_num # 顶点列表# 初始化顶点和边self.add_vertex(vertexs)self.add_edge(edges)def add_vertex(self, vertexs):"""添加顶点集合"""for i in range(self.vertex_num):self.vertex_list[i] = Vertex(vertexs[i])def add_edge(self, edges):"""添加无向边集合"""for edge in edges:# 获取顶点在列表中的索引v1_idx = self.get_vertex_index(edge[0])v2_idx = self.get_vertex_index(edge[1])if v1_idx == -1 or v2_idx == -1:raise ValueError("顶点不存在")# 为两个顶点添加边(无向图双向添加)self._add_single_edge(v1_idx, v2_idx)self._add_single_edge(v2_idx, v1_idx)def _add_single_edge(self, from_idx, to_idx):"""添加单条边到指定顶点"""new_edge = Edge(to_idx)# 头插法提高效率if self.vertex_list[from_idx].first_edge is None:self.vertex_list[from_idx].first_edge = new_edgeelse:new_edge.next_edge = self.vertex_list[from_idx].first_edgeself.vertex_list[from_idx].first_edge = new_edgedef get_vertex_index(self, data):"""根据顶点值查找索引"""for i in range(self.vertex_num):if self.vertex_list[i].data == data:return ireturn -1class DepthFirstSearch:"""深度优先遍历实现"""def __init__(self, graph):self.graph = graphself.visited = [False] * graph.vertex_num # 访问标记数组self._dfs_all() # 执行遍历def _dfs_all(self):"""遍历所有连通分量"""for i in range(self.graph.vertex_num):if not self.visited[i]:self._dfs_iterative(i)def _dfs_iterative(self, start_idx):"""迭代式DFS实现"""stack = Stack()# 访问起始顶点self.visited[start_idx] = Trueprint(self.graph.vertex_list[start_idx].data, end=" ")stack.push(start_idx)while not stack.is_empty():# 获取当前顶点未访问的邻接点current_idx = stack.peek()edge = self.graph.vertex_list[current_idx].first_edgefound = Falsewhile edge:adj_idx = edge.adj_vexif not self.visited[adj_idx]:# 访问并标记邻接点self.visited[adj_idx] = Trueprint(self.graph.vertex_list[adj_idx].data, end=" ")stack.push(adj_idx)found = Truebreakedge = edge.next_edge# 没有未访问邻接点时回溯if not found:stack.pop()# ---------------------- 测试用例 ----------------------
if __name__ == "__main__":# 测试数据(无向图)vertexs = [1, 2, 3, 4, 5, 6]edges = [[1, 2], [2, 3], [3, 4], [4, 5],[2, 4], [1, 5], [2, 5], [5, 6]]# 创建图对象graph = LinkedGraph(vertexs, edges)# 执行深度优先遍历print("DFS遍历结果:", end=" ")dfs = DepthFirstSearch(graph)# 正确输出应为:1 2 3 4 5 6 或类似连通路径1 5 6 2 4 3
广度优先遍历 Breadth First Search
-
核心思想:任意选定一个顶点v开始本次访问,在访问过v后依次访问v的待访问邻接点,并将已访问的顶点放入队列 Q中,按照Q中顶点的次序,依次访问这些已被访问过的顶点的邻接点,如果队首的顶点不存在待访问邻接点,让队首顶点出队,访问新队首的待访问邻接点,如此进行下去直至队列为空。
-
主要就是对当前顶点的所有邻接点进行访问,然后下一个,再访问其所有的邻接点,一直重复。
-
直到所有顶点均被访问过。
-
示意图
-
代码实现
# 广度优先遍历
from collections import deque # 使用高效双端队列class Vertex:"""顶点类"""def __init__(self, data):self.data = data # 顶点数据self.first_edge = None # 指向第一条邻接边class Edge:"""边类"""def __init__(self, adj_vex):self.adj_vex = adj_vex # 邻接顶点索引self.next_edge = None # 指向下一条边class LinkedGraph:"""邻接表图结构"""def __init__(self, vertices, edges):self.vertex_num = len(vertices)self.edge_num = 0self.vertex_list = [None] * self.vertex_num# 初始化顶点和边self._add_vertices(vertices)self._add_edges(edges)def _add_vertices(self, vertices):"""添加顶点集合"""for i in range(self.vertex_num):self.vertex_list[i] = Vertex(vertices[i])def _add_edges(self, edges):"""添加无向边集合"""for v1, v2 in edges:idx1 = self._get_vertex_index(v1)idx2 = self._get_vertex_index(v2)if idx1 == -1 or idx2 == -1:raise ValueError("顶点不存在")# 双向添加边(无向图)self._add_single_edge(idx1, idx2)self._add_single_edge(idx2, idx1)self.edge_num += 2def _add_single_edge(self, from_idx, to_idx):"""头插法添加单条边"""new_edge = Edge(to_idx)new_edge.next_edge = self.vertex_list[from_idx].first_edgeself.vertex_list[from_idx].first_edge = new_edgedef _get_vertex_index(self, data):"""根据顶点值查找索引"""for i in range(self.vertex_num):if self.vertex_list[i].data == data:return ireturn -1class BreadthFirstSearch:"""广度优先遍历实现"""def __init__(self, graph):self.graph = graphself.visited = [False] * graph.vertex_numself._bfs_all()def _bfs_all(self):"""遍历所有连通分量"""for i in range(self.graph.vertex_num):if not self.visited[i]:self._bfs(i)def _bfs(self, start_idx):"""单连通分量BFS"""queue = deque()# 访问起始顶点self.visited[start_idx] = Trueprint(self.graph.vertex_list[start_idx].data, end=" ")queue.append(start_idx)while queue:current_idx = queue.popleft()edge = self.graph.vertex_list[current_idx].first_edgewhile edge:neighbor_idx = edge.adj_vexif not self.visited[neighbor_idx]:self.visited[neighbor_idx] = Trueprint(self.graph.vertex_list[neighbor_idx].data, end=" ")queue.append(neighbor_idx)edge = edge.next_edge# ---------------------- 测试用例 ----------------------
if __name__ == "__main__":vertices = [1, 2, 3, 4, 5, 6]edges = [(1, 2), (2, 3), (3, 4), (4, 5),(2, 4), (1, 5), (2, 5), (5, 6)]# 创建图对象graph = LinkedGraph(vertices, edges)# 执行广度优先遍历print("BFS遍历结果:", end=" ")bfs = BreadthFirstSearch(graph)