【数据结构与算法:六、图】

第6章 图

6.1 图的定义和基本术语

6.1.1 图的定义

图(Graph)是由顶点的有限非空集合和顶点之间边的集合组成的数据结构。一个图通常表示为 G = ( V , E ) G=(V, E) G=(V,E),其中:

  • V V V 是顶点(Vertex)的集合。
  • E E E 是边(Edge)的集合。

根据边是否有方向,图可以分为:

  1. 无向图 :边没有方向,通常表示为 e = { u , v } e = \{u, v\} e={u,v}。
  2. 有向图 :边有方向,通常表示为 e = ( u , v ) e = (u, v) e=(u,v)。

此外,图还可以是带权图,即每条边或弧上附有一个权值。

6.1.2 图的基本术语

  1. 邻接点:如果两个顶点之间有一条边相连,则这两个顶点互为邻接点。

    • 在无向图中,顶点的度是与该顶点相关联的边的数目。
    • 在有向图中,顶点的入度是指指向该顶点的边的数目,出度是指从该顶点出发的边的数目。
  2. 路径:路径是指从一个顶点到另一个顶点的一个顶点序列。

    • 简单路径:路径中不重复经过顶点。
    • 回路或环:起点和终点相同的路径。
  3. 连通性

    • 在无向图中,如果任意两个顶点之间存在路径,则称该图为连通图。
    • 在有向图中,如果任意两个顶点之间存在有向路径,则称为强连通图。
  4. 完全图:在完全图中,任意两个顶点之间都存在边。

  5. 子图 :由图 G G G 的部分顶点和边组成的图。

6.2 案例引入

问题描述

考虑城市交通网络的建模。一个城市的地铁站可以看作图的顶点,两个地铁站之间的地铁线路可以看作边。建模的目标是:

  • 计算最短路径(如从 A 站到 B 站的最短车程)。
  • 寻找最小生成树(构建连接所有站点的最低成本方案)。
  • 进行图的遍历(确定从任意一个站点出发是否可以覆盖所有站点)。

6.3 图的类型定义

图可以根据边的方向和权值分类为以下类型:

  1. 无向图:边没有方向。
  2. 有向图 :边有方向,表示为 ( u , v ) (u, v) (u,v)。
  3. 带权图:每条边有一个权值,用于表示距离、成本等信息。
  4. 稀疏图:边的数量远小于顶点的数量。
  5. 稠密图:边的数量接近于顶点数量的平方。

6.4 图的存储结构

6.4.1 邻接矩阵

邻接矩阵是一种二维数组,用于存储图中顶点之间的连接关系。

定义
  • 无向图 :若 G G G 中有边 { i , j } \{i, j\} {i,j},则 A [ i ] [ j ] = 1 A[i][j] = 1 A[i][j]=1;否则 A [ i ] [ j ] = 0 A[i][j] = 0 A[i][j]=0。
  • 有向图 :若 G G G 中有边 ( i , j ) (i, j) (i,j),则 A [ i ] [ j ] = 1 A[i][j] = 1 A[i][j]=1;否则 A [ i ] [ j ] = 0 A[i][j] = 0 A[i][j]=0。
图示

无向图:

css 复制代码
    A---B
     \  |
      C-D

邻接矩阵表示:

A B C D
A 0 1 1 0
B 1 0 0 1
C 1 0 0 1
D 0 1 1 0
优缺点
  • 优点:快速判断顶点间是否有边。
  • 缺点:浪费存储空间(稀疏图中会有很多无用的 0)。

6.4.2 邻接表

邻接表是一种链表形式的存储方式,用于存储每个顶点的邻接顶点。

图示

无向图:

css 复制代码
    A---B
     \  |
      C-D

邻接表表示:

rust 复制代码
A -> B -> C
B -> A -> D
C -> A -> D
D -> B -> C
优缺点
  • 优点:适合稀疏图,节省存储空间。
  • 缺点:判断顶点间是否有边比较慢。

6.4.3 十字链表

十字链表是一种存储有向图的结构,结合了邻接表和逆邻接表的信息。

6.4.4 邻接多重表

邻接多重表是一种适合存储无向图的结构,用一个边结点同时存储两个顶点的信息。

6.5 图的遍历

6.5.1 深度优先搜索(DFS)

深度优先搜索是一种递归算法,通过尽可能深地遍历图来访问顶点。

算法描述
  1. 从起始顶点开始访问并标记为已访问。
  2. 对于当前顶点的所有未访问邻接点,递归访问它们。
实现代码
python 复制代码
def dfs(graph, start, visited=None):
    if visited is None:
        visited = set()
    visited.add(start)
    print(start, end=" ")
    for neighbor in graph[start]:
        if neighbor not in visited:
            dfs(graph, neighbor, visited)

6.5.2 广度优先搜索(BFS)

广度优先搜索是一种迭代算法,通过逐层访问图的顶点来进行遍历。

实现代码
python 复制代码
from collections import deque

def bfs(graph, start):
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        vertex = queue.popleft()
        print(vertex, end=" ")
        for neighbor in graph[vertex]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

6.6 图的应用

6.6.1 最小生成树(MST)

最小生成树(Minimum Spanning Tree)是图的一个子图,它包含所有的顶点,并且总边权值最小,且没有环。

常用算法
  1. Prim 算法:从某个顶点开始,逐步扩展最小权重的边,直到包含所有顶点。

    • 图示

      无向加权图:

      css 复制代码
            A
          / | \
         1  3  4
        /   |   \
       B----C----D
           2

      步骤:

      • 从顶点 A 开始,选择边权最小的 AB。
      • 扩展到未访问的顶点 C,选择权重为 2 的边 BC。
      • 最后添加 CD(权重为 3)。最小生成树:
      css 复制代码
            A
          / 
         1
        /  
       B----C
           2
            \
             3
              \
               D
  2. Kruskal 算法 :将所有边按权值从小到大排序,逐一选择不会形成环的边,直到构建最小生成树。应用: 网络规划、光纤布局等。

6.6.2 最短路径

Dijkstra 算法

用于求解单源最短路径问题,要求所有边权为非负。

图示:

  1. 无向加权图:
css 复制代码
    A --2-- B
    |       |
   1|       |3
    |       |
    C --1-- D
  1. 步骤:

    • 初始化:设起点为 A,初始距离为 ∞ \infty ∞。
    • 选择最短距离的顶点依次更新其他顶点距离。

最终结果:

顶点 A -> 距离 路径
A 0 A
B 2 A->B
C 1 A->C
D 2 A->C->D

代码实现:

python 复制代码
import heapq

def dijkstra(graph, start):
    pq = []  # 优先队列
    heapq.heappush(pq, (0, start))
    distances = {node: float('inf') for node in graph}
    distances[start] = 0

    while pq:
        current_distance, current_node = heapq.heappop(pq)

        if current_distance > distances[current_node]:
            continue

        for neighbor, weight in graph[current_node].items():
            distance = current_distance + weight
            if distance < distances[neighbor]:
                distances[neighbor] = distance
                heapq.heappush(pq, (distance, neighbor))
    return distances
应用场景
  • 地图导航系统(如 Google Maps)。
  • 网络路由协议(如 OSPF 协议)。

6.6.3 拓扑排序

拓扑排序用于对有向无环图(DAG)进行线性排序,使得每条有向边 ( u , v ) (u, v) (u,v) 中顶点 u u u 排在 v v v 前面。

算法步骤
  1. 计算每个顶点的入度。
  2. 将入度为 0 的顶点加入队列。
  3. 从队列中取出顶点,输出到拓扑序列,更新邻接点的入度。
  4. 重复上述步骤,直到队列为空。
图示
mathematica 复制代码
A → B → C
↓
D → E
  • 初始入度:顶点入度A0B1C1D1E2
  • 拓扑排序:A → B → D → C → E
应用场景
  • 课程安排(课程依赖关系)。
  • 项目管理中的任务调度。

6.6.4 关键路径

关键路径(Critical Path)是项目管理中的重要工具,用于分析项目中最重要的任务序列。

图示

任务节点:

scss 复制代码
(开始) → A(3天) → B(2天) → C(4天) → (结束)
           ↓
           D(6天)
分析步骤
  1. 计算每个节点的最早完成时间(Early Time, ET)
  2. 计算每个节点的最迟完成时间(Late Time, LT)
  3. 找到 ET 和 LT 相等的路径,即为关键路径。
    关键路径:A → D

6.7 案例分析与实现

案例1:最小生成树的实现(Prim 算法)

输入图:

mathematica 复制代码
    1        3
A ------- B ------ C
|         |       |
2         4       5
|         |       |
D ------- E ------ F
      6

代码实现:

python 复制代码
import heapq

def prim(graph, start):
    mst = []  # 最小生成树的边
    visited = set()
    pq = [(0, start, None)]  # (权重, 当前顶点, 前驱顶点)
    
    while pq:
        weight, current, prev = heapq.heappop(pq)
        if current in visited:
            continue
        visited.add(current)
        if prev is not None:
            mst.append((prev, current, weight))
        
        for neighbor, edge_weight in graph[current].items():
            if neighbor not in visited:
                heapq.heappush(pq, (edge_weight, neighbor, current))
    
    return mst

输入数据:

python 复制代码
graph = {
    'A': {'B': 1, 'D': 2},
    'B': {'A': 1, 'C': 3, 'E': 4},
    'C': {'B': 3, 'F': 5},
    'D': {'A': 2, 'E': 6},
    'E': {'B': 4, 'D': 6, 'F': 5},
    'F': {'C': 5, 'E': 5}
}

print(prim(graph, 'A'))

输出结果:

css 复制代码
[('A', 'B', 1), ('A', 'D', 2), ('B', 'E', 4), ('E', 'F', 5), ('B', 'C', 3)]

案例2:最短路径(Dijkstra 算法)

输入图:

css 复制代码
    A --2-- B
    |      /|
   1|   3  |
    |  /    |
    C --1-- D

代码实现:

python 复制代码
graph = {
    'A': {'B': 2, 'C': 1},
    'B': {'A': 2, 'C': 3, 'D': 3},
    'C': {'A': 1, 'B': 3, 'D': 1},
    'D': {'B': 3, 'C': 1}
}

print(dijkstra(graph, 'A'))

输出结果:

css 复制代码
{'A': 0, 'B': 2, 'C': 1, 'D': 2}

6.8 小结

本章通过图示和代码补充了图的最小生成树、最短路径、拓扑排序等核心算法的详细实现,并结合实际案例展示了它们的应用场景。通过对这些算法的学习,可以清晰地理解图在交通规划、任务调度、网络分析等领域的重要性。

相关推荐
好评笔记10 分钟前
多模态论文笔记——U-ViT(国内版DiT)
论文阅读·人工智能·深度学习·计算机视觉·aigc·transformer·u-vit
知来者逆15 分钟前
安卓NDK视觉开发——手机拍照文档边缘检测实现方法与库封装
深度学习·计算机视觉·智能手机·扫描全能王·边缘检测
小西blue21 分钟前
prompt提示词技巧
人工智能·prompt·提示词技巧·prompt技巧
爱学习的uu1 小时前
KAGGLE竞赛实战2-捷信金融违约预测竞赛-part1-数据探索及baseline建立
人工智能·python·决策树·机器学习·金融·数据挖掘·逻辑回归
Chatopera 研发团队1 小时前
Launch Linux( ubuntu14.04) GPU Acc machine in AWS
linux·人工智能·gpu算力·aws
金创想1 小时前
衡量算法效率的方法:时间复杂度、空间复杂度
算法·时间复杂度·空间复杂度·大o函数
有时间要学习1 小时前
专题十四——BFS
算法
盼小辉丶1 小时前
TensorFlow深度学习实战(4)——正则化技术详解
人工智能·深度学习·tensorflow
c的s1 小时前
朴素贝叶斯方法
python·算法·机器学习
winner88811 小时前
当算法遇到线性代数(四):奇异值分解(SVD)
线性代数·算法·奇异值分解·svd