Hello 算法9:图

https://www.hello-algo.com/chapter_graph/graph/#911

图的基本概念

  • 图由顶点和边组成,比起链表(线性数据结构)和树(分治结构),图更自由也更复杂

方向性

  • 在无向图中,边表示两个顶点之间的双向连接关系,比如微信好友
  • 在有向图中,边具有方向性,比如社交网络上的关注和被关注

连通性:连通图connected graph 和 非连通图disconnected graph

  • 连通图中,从某个顶点出发,可以到达任意顶点
  • 非连通图中,从某个顶点出发,至少有一个顶点无法到达

有权图:为边增加权重,就得到了有权图weighted graph。例如游戏中计算共同游戏时间的亲密度

其他常用术语:

  • 「邻接 adjacency」:当两顶点之间存在边相连时,称这两顶点“邻接”。
  • 「路径 path」:从顶点 A 到顶点 B 经过的边构成的序列被称为从 A 到 B 的“路径”。
  • 「度 degree」:一个顶点拥有的边数。对于有向图,「入度 in-degree」表示有多少条边指向该顶点,「出度 out-degree」表示有多少条边从该顶点指出。

图的表示

1.邻接矩阵表示:以下是一个无向图的邻接矩阵表示例子

在这里插入图片描述

它有如下特点:

  • 顶点不能和自身相连,所以对角线元素无意义
  • 无向图的两个方向等价,所以矩阵关于对角线对称
  • 将1替代为权重,即可表示有权图

时间复杂度O(1),空间复杂度O(n^2)

2.邻接表

「邻接表 adjacency list」使用n个链表来表示图,链表节点表示顶点。第i个链表对应顶点i,其中存储了该顶点的所有邻接顶点(与该顶点相连的顶点)

在这里插入图片描述

边的数量显然少于n^2,所以邻接表比起矩阵,空间占用较小;但查找效率不如矩阵。

图的常见应用

顶点图计算问题
社交网络用户好友关系潜在好友推荐
地铁站点站点间连通性最短路径问题
太阳系星体万有引力作用轨道计算

图的基础操作

图的操作分为对顶点和边的操作,邻接矩阵和邻接表的实现各有不同。

基于邻接矩阵的实现

class GraphAdjMat:
    """基于邻接矩阵实现的无向图类"""
    def __init__(self, vertices: list[int], edges: list[list[int]]):
        """构造方法"""
        # 顶点列表,元素代表“顶点值”,索引代表“顶点索引”
        self.vertices: list[int] = []
        # 邻接矩阵,行列索引对应“顶点索引”
        self.adj_mat: list[list[int]] = []
        # 添加顶点
        for val in vertices:
            self.add_vertex(val)
        # 添加边, edges代表的是顶点的索引
        for e in edges:
            self.add_edge(e[0], e[1])

    def size(self):
        """获取顶点数量"""
        return len(self.vertices)

    def add_vertex(self, val: int):
        """添加顶点"""
        n = self.size()
        # 向顶点列表添加新值
        self.vertices.append(val)
        new_row = [0] * n
        # 邻接矩阵中添加新行
        self.adj_mat.append(new_row)
        # 邻接矩阵中添加新列
        for row in self.adj_mat:
            row.append(0)

    def remove_vertex(self, index: int):
        """删除顶点"""
        if index > self.size():
            raise IndexError()
        self.vertices.pop(index)
        # 在邻接矩阵中删除索引行
        self.adj_mat.pop(index)
        # 在临界矩阵中删除索引列
        for row in self.adj_mat:
            row.pop(index)

    def add_edge(self, i: int, j: int):
        """添加边"""
        if i < 0 or j < 0 or j > self.size() or i > self.size():
            raise IndexError()
        # 参数i,j对应顶点索引
        self.adj_mat[i][j] = 1
        self.adj_mat[j][i] = 1

    def remove_edge(self, i: int, j: int):
        """删除边"""
        if i < 0 or j < 0 or j > self.size() or i > self.size():
            raise IndexError()
        self.adj_mat[i][j] = 0
        self.adj_mat[j][i] = 0

    def print(self):
        """打印邻接矩阵"""
        print("顶点:", self.vertices)
        print("邻接矩阵:")
        for row in self.adj_mat:
            print(row)

基于邻接表的实现

略。https://github.com/h-kayotin/hanayo_homework/blob/master/Hello_算法/图/02_图_邻接表.py

图的遍历

图的遍历可以看做是树的一种特殊情况

广度优先遍历

由近及远,由某个顶点出发,始终优先访问最近的顶点,然后一层层向外扩张。

通常借助队列来实现

def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
    """广度优先遍历"""
    # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
    # 顶点遍历序列
    res = []
    # 记录已经访问过的顶点
    visited = set[Vertex]([start_vet])
    # 队列用于实现dfs
    que = deque[Vertex]([start_vet])
    # 循环访问
    while len(que):
        vet = que.popleft()
        res.append(vet)
        # 遍历所有相邻顶点
        for adj_vet in graph.adj_list[vet]:
            # 跳过已访问的顶点
            if adj_vet in visited:
                continue
            # 入队未访问过的顶点
            que.append(adj_vet)
            visited.add(adj_vet)
    return res

广度优先的遍历序列不是唯一的

深度优先遍历

从某一点出发,优先走到底,无路可走再回头。

通常借助递归实现

def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex):
    """深度优先遍历辅助函数"""
    res.append(vet)  # 记录访问顶点
    visited.add(vet)  # 标记该顶点已被访问
    # 遍历该顶点所有相邻顶点
    for adj_vet in graph.adj_list[vet]:
        if adj_vet in visited:
            continue
        # 递归访问相邻节点
        dfs(graph, visited, res, adj_vet)

def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]:
    """深度优先遍历"""
    # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点
    # 顶点遍历序列
    res = []
    # 哈希表,用于记录已被访问过的顶点
    visited = set[Vertex]()
    dfs(graph, visited, res, start_vet)
    return res

深度遍历的序列也不是唯一的

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值