【17】数据结构之图的遍历篇章

图的遍历

从图中某一个顶点出发,沿着一些边访遍图中所有的顶点,且使用每个顶点仅被访问一次,这个过程称为图的遍历.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) == 0

    def push(self, item):
        """元素入栈"""
        self.items.append(item)

    def pop(self):
        """元素出栈并返回"""
        if not self.is_empty():
            return self.items.pop()
        return None

    def peek(self):
        """查看栈顶元素"""
        if not self.is_empty():
            return self.items[-1]
        return None


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, 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_edge
        else:
            new_edge.next_edge = self.vertex_list[from_idx].first_edge
            self.vertex_list[from_idx].first_edge = new_edge

    def get_vertex_index(self, data):
        """根据顶点值查找索引"""
        for i in range(self.vertex_num):
            if self.vertex_list[i].data == data:
                return i
        return -1


class DepthFirstSearch:
    """深度优先遍历实现"""

    def __init__(self, graph):
        self.graph = graph
        self.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] = True
        print(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_edge
            found = False

            while edge:
                adj_idx = edge.adj_vex
                if not self.visited[adj_idx]:
                    # 访问并标记邻接点
                    self.visited[adj_idx] = True
                    print(self.graph.vertex_list[adj_idx].data, end=" ")
                    stack.push(adj_idx)
                    found = True
                    break
                edge = 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 = 0
        self.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 += 2

    def _add_single_edge(self, from_idx, to_idx):
        """头插法添加单条边"""
        new_edge = Edge(to_idx)
        new_edge.next_edge = self.vertex_list[from_idx].first_edge
        self.vertex_list[from_idx].first_edge = new_edge

    def _get_vertex_index(self, data):
        """根据顶点值查找索引"""
        for i in range(self.vertex_num):
            if self.vertex_list[i].data == data:
                return i
        return -1


class BreadthFirstSearch:
    """广度优先遍历实现"""

    def __init__(self, graph):
        self.graph = graph
        self.visited = [False] * graph.vertex_num
        self._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] = True
        print(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_edge

            while edge:
                neighbor_idx = edge.adj_vex
                if not self.visited[neighbor_idx]:
                    self.visited[neighbor_idx] = True
                    print(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)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_x_w

你的肯定是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值