跳转至

图算法(Graph Algorithms)完全详解 - 从零到网络流

重要性:⭐⭐⭐⭐⭐ 难度:⭐⭐⭐⭐ 学习时间:7-10天 前置知识:树、递归、队列、栈


📚 目录

  1. 图的基础
  2. 图的表示方法
  3. 图的遍历
  4. 最短路径算法
  5. 最小生成树
  6. 拓扑排序
  7. A*算法
  8. 网络流初步
  9. 经典面试题
  10. 实战应用

图的基础

图算法示意图

什么是图?

是一种由顶点(Vertex)边(Edge)组成的非线性数据结构,用于表示多对多的关系。

生活中的例子

想象一个社交网络

Text Only
        小明
       / |  \
      /  |   \
    小李 小王 小红
     |    |    |
    小张  小陈 小刘
     |
   小赵

顶点:每个人
边:好友关系
  • 顶点(节点):每个人
  • :好友关系
  • :好友数量(小明有3个好友)
  • 路径:从小赵到小红:小赵 → 小张 → 小李 → 小明 → 小红

为什么学图?

应用极其广泛: - 社交网络(Facebook、Twitter、微信) - 地图导航(GPS、高德地图) - 网络路由(互联网路由协议) - 推荐系统(淘宝、Netflix) - 游戏AI(寻路、决策) - 编译器(依赖关系分析) - 项目管理(任务调度)

面试常考: - 图的遍历 - 最短路径 - 最小生成树 - 拓扑排序 - 网络流

图的基本概念

Text Only
术语图解:

       A           ← 顶点(Vertex/Node)
     / |  \
    /  |   \      ← 边(Edge)
   B---C---D
   |       |
   E-------F       ← 路径(A→B→E→F)

关键概念:
- 顶点:A, B, C, D, E, F
- 边:(A,B), (A,C), (A,D), (B,C), (B,E), (C,D), (D,F), (E,F)
- 无向图:边没有方向
- A的度:3(连接到B、C、D)
- 路径A→E:A→B→E(长度2)
- 简单路径:顶点不重复
- 环:B→C→D→F→E→B

图的分类

Text Only
1. 无向图 vs 有向图

无向图:          有向图:
    A               A→B→C
   / \              ↓  ↓
  B---C             D→E
  边无方向          边有方向(箭头)

2. 无权图 vs 带权图

无权图:          带权图:
    A                A --5--> B
   / \              /|        |
  B   C            2 |        | 3
                    D        3 C
                    边有权重(距离、费用等)

3. 简单图 vs 多重图

简单图:          多重图:
    A                A
   / \              /|\
  B---C            / | \
                    B  C
                    两个节点间有多条边

4. 连通图 vs 非连通图

连通图:          非连通图:
    A               A    B
   / \             / \    |
  B---C            D   E   F
  所有节点连通      有孤立节点

图的存储结构对比

表示方法 优点 缺点 适用场景
邻接矩阵 O(1)查询边存在
简单实现
空间O(V²)浪费
稀疏图效率低
稠密图
邻接表 空间O(V+E)
遍历高效
查询边O(度数) 稀疏图 ⭐
十字链表 有向图高效 实现复杂 有向图
邻接多重表 无向图高效 实现复杂 无向图

图的表示方法

方法1:邻接矩阵(Adjacency Matrix)

原理:用二维矩阵存储图,matrix[i][j]表示顶点i到顶点j的边。

无向图的邻接矩阵

Python
class GraphMatrix:
    """使用邻接矩阵实现无向图"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        # 创建num_vertices × num_vertices的矩阵
        self.matrix = [[0] * num_vertices for _ in range(num_vertices)]

    def add_edge(self, i, j, weight=1):
        """添加边(无向图是对称的)"""
        if 0 <= i < self.num_vertices and 0 <= j < self.num_vertices:
            self.matrix[i][j] = weight
            self.matrix[j][i] = weight  # 无向图是对称的
            print(f"添加边: {i} ←→ {j} (权重={weight})")

    def remove_edge(self, i, j):
        """删除边"""
        if 0 <= i < self.num_vertices and 0 <= j < self.num_vertices:
            self.matrix[i][j] = 0
            self.matrix[j][i] = 0
            print(f"删除边: {i} ←→ {j}")

    def has_edge(self, i, j):
        """检查边是否存在:O(1)"""
        return self.matrix[i][j] != 0

    def get_neighbors(self, v):
        """获取顶点v的所有邻居:O(V)"""
        neighbors = []
        for i in range(self.num_vertices):
            if self.matrix[v][i] != 0:
                neighbors.append((i, self.matrix[v][i]))
        return neighbors

    def print_matrix(self):
        """打印邻接矩阵"""
        print("\n邻接矩阵:")
        print("   ", end="")
        for i in range(self.num_vertices):
            print(f"{i:3}", end="")
        print()

        for i in range(self.num_vertices):
            print(f"{i:2} ", end="")
            for j in range(self.num_vertices):
                print(f"{self.matrix[i][j]:3}", end="")
            print()

# 测试
graph = GraphMatrix(5)
print("=" * 60)
print("邻接矩阵测试")
print("=" * 60)

# 添加边(构建如下图)
#     0
#    / \
#   1---2
#   |   |
#   3---4

graph.add_edge(0, 1)
graph.add_edge(0, 2)
graph.add_edge(1, 2)
graph.add_edge(1, 3)
graph.add_edge(2, 4)
graph.add_edge(3, 4)

graph.print_matrix()

# 输出:
# ============================================================
# 邻接矩阵测试
# ============================================================
# 添加边: 0 ←→ 1 (权重=1)
# 添加边: 0 ←→ 2 (权重=1)
# 添加边: 1 ←→ 2 (权重=1)
# 添加边: 1 ←→ 3 (权重=1)
# 添加边: 2 ←→ 4 (权重=1)
# 添加边: 3 ←→ 4 (权重=1)
#
# 邻接矩阵:
#       0  1  2  3  4
#  0   0  1  1  0  0
#  1   1  0  1  1  0
#  2   1  1  0  0  1
#  3   0  1  0  0  1
#  4   0  0  1  1  0
#
# 特点:
# - 对角线为0(自己到自己)
# - 对称矩阵(无向图)
# - 第i行的和 = 顶点i的度

带权有向图的邻接矩阵

Python
class WeightedDiGraphMatrix:
    """带权有向图的邻接矩阵"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        # 使用float('inf')表示无穷大(没有边)
        self.matrix = [[float('inf')] * num_vertices for _ in range(num_vertices)]
        # 对角线设为0(自己到自己的距离为0)
        for i in range(num_vertices):
            self.matrix[i][i] = 0

    def add_edge(self, i, j, weight):
        """添加有向边"""
        if 0 <= i < self.num_vertices and 0 <= j < self.num_vertices:
            self.matrix[i][j] = weight
            print(f"添加边: {i}{j} (权重={weight})")

    def print_matrix(self):
        """打印邻接矩阵"""
        print("\n邻接矩阵(带权有向图):")
        print("     ", end="")
        for i in range(self.num_vertices):
            print(f"{i:6}", end="")
        print()

        for i in range(self.num_vertices):
            print(f"{i:2} ", end="")
            for j in range(self.num_vertices):
                val = self.matrix[i][j]
                if val == float('inf'):
                    print(f"   INF", end="")
                else:
                    print(f"{val:6}", end="")
            print()

# 测试
print("\n" + "=" * 60)
print("带权有向图测试")
print("=" * 60)

wg = WeightedDiGraphMatrix(4)

# 构建如下带权有向图:
#     10     20
#  0 ----> 1 ----> 2
#  ^       |      /^
#  |5     |15   /1
#  3 ---->-----   |
#      8
#
# 注:箭头表示边的方向

wg.add_edge(0, 1, 10)
wg.add_edge(1, 2, 20)
wg.add_edge(1, 3, 15)
wg.add_edge(3, 0, 5)
wg.add_edge(3, 1, 8)
wg.add_edge(2, 3, 1)

wg.print_matrix()

# 输出:
# ============================================================
# 带权有向图测试
# ============================================================
# 添加边: 0 → 1 (权重=10)
# 添加边: 1 → 2 (权重=20)
# 添加边: 1 → 3 (权重=15)
# 添加边: 3 → 0 (权重=5)
# 添加边: 3 → 1 (权重=8)
# 添加边: 2 → 3 (权重=1)
#
# 邻接矩阵(带权有向图):
#          0      1      2      3
#  0       0     10    INF    INF
#  1     INF       0     20     15
#  2     INF     INF      0      1
#  3       5      8    INF      0
#
# 特点:
# - 非对称矩阵(有向图)
# - INF表示没有边

邻接矩阵优缺点

优点: - 查询边是否存在:O(1) - 判断边的权重:O(1) - 实现简单

缺点: - 空间复杂度:O(V²) - 浪费空间 - 遍历所有邻居:O(V) - 效率低 - 不适合稀疏图(边数远小于V²)


方法2:邻接表(Adjacency List)⭐

原理:为每个顶点维护一个列表,存储所有相邻的顶点。

无向图的邻接表

Python
class GraphAdjList:
    """使用邻接表实现无向图"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        # 每个顶点对应一个列表,存储邻居
        self.adj_list = [[] for _ in range(num_vertices)]

    def add_edge(self, i, j, weight=1):
        """添加边"""
        if 0 <= i < self.num_vertices and 0 <= j < self.num_vertices:
            self.adj_list[i].append((j, weight))
            self.adj_list[j].append((i, weight))  # 无向图要双向添加
            print(f"添加边: {i} ←→ {j} (权重={weight})")

    def remove_edge(self, i, j):
        """删除边"""
        if 0 <= i < self.num_vertices and 0 <= j < self.num_vertices:
            # 从i的邻居列表中删除j
            self.adj_list[i] = [(v, w) for v, w in self.adj_list[i] if v != j]
            # 从j的邻居列表中删除i
            self.adj_list[j] = [(v, w) for v, w in self.adj_list[j] if v != i]
            print(f"删除边: {i} ←→ {j}")

    def has_edge(self, i, j):
        """检查边是否存在:O(度数)"""
        for v, w in self.adj_list[i]:
            if v == j:
                return True
        return False

    def get_neighbors(self, v):
        """获取顶点v的所有邻居:O(度数)"""
        return self.adj_list[v]

    def print_adj_list(self):
        """打印邻接表"""
        print("\n邻接表:")
        for i in range(self.num_vertices):
            neighbors = ", ".join([f"({v},w={w})" for v, w in self.adj_list[i]])
            print(f"  {i}: [{neighbors}]")

# 测试
graph = GraphAdjList(5)
print("=" * 60)
print("邻接表测试")
print("=" * 60)

# 构建相同的图
graph.add_edge(0, 1)
graph.add_edge(0, 2)
graph.add_edge(1, 2)
graph.add_edge(1, 3)
graph.add_edge(2, 4)
graph.add_edge(3, 4)

graph.print_adj_list()

# 输出:
# ============================================================
# 邻接表测试
# ============================================================
# 添加边: 0 ←→ 1 (权重=1)
# 添加边: 0 ←→ 2 (权重=1)
# 添加边: 1 ←→ 2 (权重=1)
# 添加边: 1 ←→ 3 (权重=1)
# 添加边: 2 ←→ 4 (权重=1)
# 添加边: 3 ←→ 4 (权重=1)
#
# 邻接表:
#   0: [(1,w=1), (2,w=1)]
#   1: [(0,w=1), (2,w=1), (3,w=1)]
#   2: [(0,w=1), (1,w=1), (4,w=1)]
#   3: [(1,w=1), (4,w=1)]
#   4: [(2,w=1), (3,w=1)]

Python字典实现邻接表(更灵活)

Python
class GraphDict:
    """使用字典实现邻接表(更灵活)"""

    def __init__(self):
        # 字典:顶点 -> [(邻居, 权重), ...]
        self.graph = {}

    def add_vertex(self, vertex):
        """添加顶点"""
        if vertex not in self.graph:
            self.graph[vertex] = []
            print(f"添加顶点: {vertex}")

    def add_edge(self, v1, v2, weight=1, directed=False):
        """添加边"""
        if v1 not in self.graph or v2 not in self.graph:
            print(f"错误:顶点不存在")
            return

        self.graph[v1].append((v2, weight))
        print(f"添加边: {v1}{v2} (权重={weight})")

        if not directed:  # 无向图,反向也要添加
            self.graph[v2].append((v1, weight))
            print(f"添加边: {v2}{v1} (权重={weight})")

    def get_neighbors(self, vertex):
        """获取邻居"""
        return self.graph.get(vertex, [])

    def print_graph(self):
        """打印图"""
        print("\n图的邻接表表示:")
        for vertex in sorted(self.graph.keys()):
            neighbors = ", ".join([f"{v}(w={w})" for v, w in self.graph[vertex]])
            print(f"  {vertex}: [{neighbors}]")

# 测试:城市交通网络
print("\n" + "=" * 60)
print("城市交通网络(带权无向图)")
print("=" * 60)

cities = GraphDict()

# 添加城市
for city in ['北京', '上海', '广州', '深圳', '成都']:
    cities.add_vertex(city)

# 添加路线(距离单位:公里)
cities.add_edge('北京', '上海', 1200)
cities.add_edge('北京', '成都', 1800)
cities.add_edge('上海', '广州', 1400)
cities.add_edge('广州', '深圳', 140)
cities.add_edge('成都', '广州', 1600)

cities.print_graph()

# 输出:
# ============================================================
# 城市交通网络(带权无向图)
# ============================================================
# 添加顶点: 北京
# 添加顶点: 上海
# 添加顶点: 广州
# 添加顶点: 深圳
# 添加顶点: 成都
# 添加边: 北京 → 上海 (权重=1200)
# 添加边: 上海 → 北京 (权重=1200)
# 添加边: 北京 → 成都 (权重=1800)
# 添加边: 成都 → 北京 (权重=1800)
# 添加边: 上海 → 广州 (权重=1400)
# 添加边: 广州 → 上海 (权重=1400)
# 添加边: 广州 → 深圳 (权重=140)
# 添加边: 深圳 → 广州 (权重=140)
# 添加边: 成都 → 广州 (权重=1600)
# 添加边: 广州 → 成都 (权重=1600)
#
# 图的邻接表表示:
#   上海: [(北京,w=1200), (广州,w=1400)]
#   北京: [(上海,w=1200), (成都,w=1800)]
#   广州: [(上海,w=1400), (深圳,w=140), (成都,w=1600)]
#   成都: [(北京,w=1800), (广州,w=1600)]
#   深圳: [(广州,w=140)]

邻接表优缺点

优点: - 空间复杂度:O(V+E) - 节省空间 - 遍历邻居:O(度数) - 高效 - 适合稀疏图

缺点: - 查询边是否存在:O(度数) - 稍微复杂一点

空间复杂度对比

Text Only
对于V个顶点、E条边的图:

邻接矩阵:O(V²)
  - 稠密图(E ≈ V²):合适
  - 稀疏图(E ≈ V):浪费

邻接表:O(V+E)
  - 稀疏图:高效 ⭐
  - 稠密图:也可以

示例:
V = 1000个顶点
E = 5000条边(稀疏图)

邻接矩阵:1000 × 1000 = 1,000,000个存储单元
邻接表:1000 + 5000 × 2 = 11,000个存储单元(节省98.9%!)

图的遍历

为什么需要图遍历?

应用场景: - 搜索路径(导航、游戏AI) - 连通性检查(网络分析) - 可达性分析(社交网络) - 网页爬虫(图遍历) - 垃圾回收(根搜索)

核心思想:一条路走到黑,撞墙了再回退。

Text Only
DFS过程演示:

     A
    /|\
   B C D
  /|   |
 E F   G
 |

从A开始DFS:
1. 访问A
2. 访问A的第一个邻居B
3. 访问B的第一个邻居E
4. E没有邻居,回退到B
5. 访问B的下一个邻居F
6. F没有邻居,回退到B
7. B的所有邻居都访问过了,回退到A
8. 访问A的下一个邻居C
9. C没有邻居,回退到A
10. 访问A的下一个邻居D
11. 访问D的邻居G

访问顺序:A → B → E → F → C → D → G

DFS递归实现

Python
class GraphDFS:
    """图的DFS遍历"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.adj_list = [[] for _ in range(num_vertices)]

    def add_edge(self, i, j):
        """添加边"""
        self.adj_list[i].append(j)
        self.adj_list[j].append(i)

    def dfs_recursive(self, start=0):
        """
        DFS递归实现
        时间:O(V+E)
        空间:O(V)(递归栈 + visited数组)
        """
        visited = [False] * self.num_vertices
        result = []

        def dfs_helper(vertex):
            visited[vertex] = True
            result.append(vertex)
            print(f"  访问顶点: {vertex}")

            # 递归访问所有未访问的邻居
            for neighbor in self.adj_list[vertex]:
                if not visited[neighbor]:
                    dfs_helper(neighbor)

        print(f"\n从顶点{start}开始DFS(递归):")
        dfs_helper(start)
        return result

    def dfs_iterative(self, start=0):
        """
        DFS迭代实现(使用栈)
        时间:O(V+E)
        空间:O(V)
        """
        visited = [False] * self.num_vertices
        result = []
        stack = [start]

        print(f"\n从顶点{start}开始DFS(迭代):")

        while stack:
            vertex = stack.pop()

            if not visited[vertex]:
                visited[vertex] = True
                result.append(vertex)
                print(f"  访问顶点: {vertex}")

                # 将邻居加入栈(倒序加入,保证顺序一致)
                for neighbor in reversed(self.adj_list[vertex]):
                    if not visited[neighbor]:
                        stack.append(neighbor)

        return result

    def dfs_all_components(self):
        """遍历所有连通分量"""
        visited = [False] * self.num_vertices
        result = []
        component_id = 0

        print("\n遍历所有连通分量:")

        for vertex in range(self.num_vertices):
            if not visited[vertex]:
                print(f"\n连通分量 {component_id}:")
                component = []

                def dfs(v):
                    visited[v] = True
                    component.append(v)
                    print(f"  访问: {v}")

                    for neighbor in self.adj_list[v]:
                        if not visited[neighbor]:
                            dfs(neighbor)

                dfs(vertex)
                result.append(component)
                component_id += 1

        return result

# 测试
print("=" * 60)
print("DFS遍历测试")
print("=" * 60)

# 构建如下图:
#       0
#      /|\
#     1 2 3
#    /|   |
#   4 5   6
#
#  7-8(孤立连通分量)

graph = GraphDFS(9)
edges = [(0,1), (0,2), (0,3), (1,4), (1,5), (3,6), (7,8)]
for i, j in edges:
    graph.add_edge(i, j)

result1 = graph.dfs_recursive(0)
print(f"\nDFS递归结果: {result1}")

result2 = graph.dfs_iterative(0)
print(f"\nDFS迭代结果: {result2}")

components = graph.dfs_all_components()
print(f"\n共找到 {len(components)} 个连通分量")

# 输出:
# ============================================================
# DFS遍历测试
# ============================================================
#
# 从顶点0开始DFS(递归):
#   访问顶点: 0
#   访问顶点: 1
#   访问顶点: 4
#   访问顶点: 5
#   访问顶点: 2
#   访问顶点: 3
#   访问顶点: 6
#
# DFS递归结果: [0, 1, 4, 5, 2, 3, 6]
#
# 从顶点0开始DFS(迭代):
#   访问顶点: 0
#   访问顶点: 1
#   访问顶点: 4
#   访问顶点: 5
#   访问顶点: 2
#   访问顶点: 3
#   访问顶点: 6
#
# DFS迭代结果: [0, 1, 4, 5, 2, 3, 6]
#
# 遍历所有连通分量:
#
# 连通分量 0:
#   访问: 0
#   访问: 1
#   访问: 4
#   访问: 5
#   访问: 2
#   访问: 3
#   访问: 6
#
# 连通分量 1:
#   访问: 7
#   访问: 8
#
# 共找到 2 个连通分量

核心思想:层层推进,像水波纹一样扩散。

Text Only
BFS过程演示:

     A           层0: {A}
    /|\
   B C D         层1: {B, C, D}
  /|   |
 E F   G         层2: {E, F, G}
 |

从A开始BFS:
1. 访问A(层0)
2. 访问A的所有邻居:B, C, D(层1)
3. 访问B的所有未访问邻居:E, F(层2)
4. 访问C的邻居:无
5. 访问D的邻居:G(层2)

访问顺序:A → B → C → D → E → F → G

BFS实现

Python
from collections import deque

class GraphBFS:
    """图的BFS遍历"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.adj_list = [[] for _ in range(num_vertices)]

    def add_edge(self, i, j):
        """添加边"""
        self.adj_list[i].append(j)
        self.adj_list[j].append(i)

    def bfs(self, start=0):
        """
        BFS实现
        时间:O(V+E)
        空间:O(V)
        """
        visited = [False] * self.num_vertices
        result = []
        queue = deque([start])
        visited[start] = True

        print(f"从顶点{start}开始BFS:")

        while queue:
            vertex = queue.popleft()
            result.append(vertex)
            print(f"  访问顶点: {vertex}")

            # 将所有未访问的邻居加入队列
            for neighbor in self.adj_list[vertex]:
                if not visited[neighbor]:
                    visited[neighbor] = True
                    queue.append(neighbor)

        return result

    def bfs_level_by_level(self, start=0):
        """BFS分层遍历(更详细)"""
        visited = [False] * self.num_vertices
        queue = deque([start])
        visited[start] = True
        level = 0

        print(f"\n从顶点{start}开始BFS(分层):")

        while queue:
            level_size = len(queue)
            print(f"\n{level}层: ", end="")

            for _ in range(level_size):
                vertex = queue.popleft()
                print(f"{vertex}", end=" ")

                for neighbor in self.adj_list[vertex]:
                    if not visited[neighbor]:
                        visited[neighbor] = True
                        queue.append(neighbor)

            level += 1

        print()

    def bfs_shortest_path(self, start, end):
        """BFS求最短路径(无权图)"""
        if start == end:
            return [start], 0

        visited = [False] * self.num_vertices
        parent = [-1] * self.num_vertices  # 记录路径
        queue = deque([start])
        visited[start] = True

        print(f"\n求从{start}{end}的最短路径:")

        while queue:
            vertex = queue.popleft()

            for neighbor in self.adj_list[vertex]:
                if not visited[neighbor]:
                    visited[neighbor] = True
                    parent[neighbor] = vertex
                    queue.append(neighbor)

                    if neighbor == end:
                        # 找到目标,回溯路径
                        path = []
                        current = end
                        while current != -1:
                            path.append(current)
                            current = parent[current]
                        path.reverse()

                        print(f"  路径: {path}")
                        print(f"  长度: {len(path) - 1}")
                        return path, len(path) - 1

        print("  未找到路径")
        return None, -1

# 测试
print("\n" + "=" * 60)
print("BFS遍历测试")
print("=" * 60)

# 使用相同的图
graph = GraphBFS(9)
edges = [(0,1), (0,2), (0,3), (1,4), (1,5), (3,6), (7,8)]
for i, j in edges:
    graph.add_edge(i, j)

result = graph.bfs(0)
print(f"\nBFS结果: {result}")

graph.bfs_level_by_level(0)

path, length = graph.bfs_shortest_path(0, 6)
print(f"\n最短路径验证:")
print(f"  从0到6: {path} (长度={length})")

# 输出:
# ============================================================
# BFS遍历测试
# ============================================================
# 从顶点0开始BFS:
#   访问顶点: 0
#   访问顶点: 1
#   访问顶点: 2
#   访问顶点: 3
#   访问顶点: 4
#   访问顶点: 5
#   访问顶点: 6
#
# BFS结果: [0, 1, 2, 3, 4, 5, 6]
#
# 从顶点0开始BFS(分层):
#
# 第0层: 0
# 第1层: 1 2 3
# 第2层: 4 5 6
#
# 求从0到6的最短路径:
#   路径: [0, 3, 6]
#   长度: 2
#
# 最短路径验证:
#   从0到6: [0, 3, 6] (长度=2)

DFS vs BFS 对比

特性 DFS BFS
数据结构 栈(递归) 队列
搜索方式 深入到底 层层推进
路径 不一定最短 最短路径(无权图)
空间 O(h) 树高度 O(w) 最宽层
应用 拓扑排序、强连通分量 最短路径、连通性
实现 递归简单 迭代
Text Only
图解对比:

     A
    /|\
   B C D
  /|   |
 E F   G

DFS: A→B→E→F→C→D→G(一条道走到黑)
BFS: A→B→C→D→E→F→G(层层推进)

最短路径算法

问题定义

给定:带权有向图 G = (V, E),源点 s :从 s 到所有其他顶点的最短路径

三大经典算法对比

算法 图类型 边权要求 时间复杂度 适用场景
Dijkstra 有向/无向 非负 O((V+E)log V) 非负权图 ⭐
Bellman-Ford 有向 可负 O(VE) 有负权边
Floyd-Warshall 有向/无向 可负 O(V³) 所有顶点对

算法1:Dijkstra算法 ⭐⭐⭐⭐⭐

核心思想:贪心,每次选择距离最近的未访问顶点。

适用条件: - 边权非负 - 可以是有向图或无向图

Dijkstra算法图解

Text Only
示例图(有向图,边权标注在箭头旁):

   A --10--> B --30--> C
   |         |        ^
   5        15       /
   |         |      1
   v         v    /
   D ---8--> B   D --1--> C

邻接表表示:
  A → B(10), D(5)
  B → C(30), D(15)
  D → B(8),  C(1)
  C → (无出边)

求从A到所有顶点的最短路径:

初始:dist[A]=0, 其他=∞
      visited={}

步骤1:选择A(dist最小)
  - A → B: 10 < ∞, 更新 dist[B]=10
  - A → D: 5 < ∞, 更新 dist[D]=5
  - visited={A}

步骤2:选择D(dist最小且未访问)
  - D → B: 5+8=13 > 10, 不更新
  - D → C: 5+1=6 < ∞, 更新 dist[C]=6
  - visited={A, D}

步骤3:选择C(dist最小且未访问)
  - C 没有可松弛的出边
  - visited={A, D, C}

步骤4:选择B(dist最小且未访问)
  - B → C: 10+30=40 > 6, 不更新
  - B → D: 10+15=25 > 5, 不更新
  - visited={A, D, C, B}

最终结果:
A到A: 0
A到B: 10 (路径: A→B)
A到C: 6  (路径: A→D→C)
A到D: 5  (路径: A→D)

Dijkstra实现

Python
import heapq

class DijkstraGraph:
    """Dijkstra算法实现"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.adj_list = [[] for _ in range(num_vertices)]

    def add_edge(self, i, j, weight, directed=True):
        """添加边"""
        self.adj_list[i].append((j, weight))
        if not directed:
            self.adj_list[j].append((i, weight))

    def dijkstra(self, start):
        """
        Dijkstra算法(优先队列优化)
        时间:O((V+E)log V)
        空间:O(V)
        """
        # 初始化距离
        dist = [float('inf')] * self.num_vertices
        dist[start] = 0

        # 记录路径
        parent = [-1] * self.num_vertices

        # 最小堆:(距离, 顶点)
        heap = [(0, start)]
        visited = [False] * self.num_vertices

        print(f"\n从顶点{start}开始Dijkstra算法:")
        print("=" * 60)

        while heap:
            current_dist, vertex = heapq.heappop(heap)

            # 如果已经访问过,跳过
            if visited[vertex]:
                continue

            visited[vertex] = True
            print(f"\n访问顶点 {vertex} (当前距离={current_dist})")

            # 遍历所有邻居
            for neighbor, weight in self.adj_list[vertex]:
                if not visited[neighbor]:
                    new_dist = current_dist + weight

                    # 松弛操作:如果找到更短的路径
                    if new_dist < dist[neighbor]:
                        dist[neighbor] = new_dist
                        parent[neighbor] = vertex
                        heapq.heappush(heap, (new_dist, neighbor))
                        print(f"  更新: {vertex}{neighbor} (新距离={new_dist})")

        return dist, parent

    def print_path(self, parent, start, end):
        """打印路径"""
        if parent[end] == -1 and start != end:
            print(f"  {start}{end}: 不可达")
            return

        path = []
        current = end
        while current != -1:
            path.append(current)
            current = parent[current]
        path.reverse()

        print(f"  最短路径: {' → '.join(map(str, path))}")

    def dijkstra_detailed(self, start):
        """Dijkstra详细版(展示每一步)"""
        dist, parent = self.dijkstra(start)

        print("\n" + "=" * 60)
        print("最终结果:")
        print("=" * 60)

        for i in range(self.num_vertices):
            if dist[i] == float('inf'):
                print(f"{start}{i}: 不可达")
            else:
                print(f"{start}{i}: 距离={dist[i]}", end="")
                if i != start:
                    print(", 路径=", end="")
                    self.print_path(parent, start, i)
                else:
                    print()

# 测试
print("=" * 60)
print("Dijkstra算法测试")
print("=" * 60)

graph = DijkstraGraph(5)

# 构建图:
#     10    30
#  0 ----> 1 ----> 2
#  |       |      /^
# 5|      15|   /1
#  v       v  /
#  3 ---->----
#      8
# 注:顶点4未连接任何边,用于展示不可达情况

graph.add_edge(0, 1, 10)
graph.add_edge(0, 3, 5)
graph.add_edge(1, 2, 30)
graph.add_edge(1, 3, 15)
graph.add_edge(3, 2, 1)
graph.add_edge(3, 1, 8)

dist, parent = graph.dijkstra(0)

# 输出:
# ============================================================
# Dijkstra算法测试
# ============================================================
#
# 从顶点0开始Dijkstra算法:
# =============================================================
#
# 访问顶点 0 (当前距离=0)
#   更新: 0 → 1 (新距离=10)
#   更新: 0 → 3 (新距离=5)
#
# 访问顶点 3 (当前距离=5)
#   更新: 3 → 2 (新距离=6)
#   (检查 3 → 1: 距离=13 > 当前dist[1]=10,不更新)
#
# 访问顶点 2 (当前距离=6)
#
# 访问顶点 1 (当前距离=10)
#
# =============================================================
# 最终结果:
# =============================================================
# 0 → 0: 距离=0
# 0 → 1: 距离=10, 路径=  最短路径: 0 → 1
# 0 → 2: 距离=6, 路径=  最短路径: 0 → 3 → 2
# 0 → 3: 距离=5, 路径=  最短路径: 0 → 3
# 0 → 4: 不可达

Dijkstra算法总结

优点: - 时间复杂度优秀:O((V+E)log V) - 适用于稠密图和稀疏图 - 实现相对简单

缺点: - 不能处理负权边 - 无法检测负权环


算法2:Bellman-Ford算法

核心思想:松弛所有边V-1次

适用条件: - 可以有负权边 - 可以检测负权环

Bellman-Ford实现

Python
class BellmanFordGraph:
    """Bellman-Ford算法实现"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.edges = []  # 边列表:(u, v, weight)

    def add_edge(self, u, v, weight):
        """添加边"""
        self.edges.append((u, v, weight))
        print(f"添加边: {u}{v} (权重={weight})")

    def bellman_ford(self, start):
        """
        Bellman-Ford算法
        时间:O(VE)
        空间:O(V)
        """
        # 初始化
        dist = [float('inf')] * self.num_vertices
        dist[start] = 0
        parent = [-1] * self.num_vertices

        print(f"\n从顶点{start}开始Bellman-Ford算法:")
        print("=" * 60)

        # 松弛所有边 V-1 次
        for i in range(self.num_vertices - 1):
            print(f"\n{i+1}轮松弛:")
            updated = False

            for u, v, weight in self.edges:
                if dist[u] != float('inf') and dist[u] + weight < dist[v]:
                    dist[v] = dist[u] + weight
                    parent[v] = u
                    updated = True
                    print(f"  松弛: {u}{v}, 更新dist[{v}]={dist[v]}")

            if not updated:
                print("  无更新,提前结束")
                break

        # 检测负权环
        print("\n检测负权环:")
        for u, v, weight in self.edges:
            if dist[u] != float('inf') and dist[u] + weight < dist[v]:
                print("  发现负权环!")
                return None, None

        print("  无负权环 ✓")

        return dist, parent

# 测试
print("\n" + "=" * 60)
print("Bellman-Ford算法测试")
print("=" * 60)

graph = BellmanFordGraph(5)

# 构建图(含负权边)
graph.add_edge(0, 1, 6)
graph.add_edge(0, 2, 7)
graph.add_edge(1, 2, 8)
graph.add_edge(1, 3, -4)
graph.add_edge(1, 4, 5)
graph.add_edge(2, 3, 9)
graph.add_edge(2, 4, -3)
graph.add_edge(3, 4, 7)
graph.add_edge(4, 1, -2)

dist, parent = graph.bellman_ford(0)

if dist:
    print("\n最终距离:")
    for i, d in enumerate(dist):  # enumerate同时获取索引和值
        if d != float('inf'):
            print(f"  0 → {i}: {d}")

# 输出:
# ============================================================
# Bellman-Ford算法测试
# ============================================================
# 添加边: 0 → 1 (权重=6)
# 添加边: 0 → 2 (权重=7)
# 添加边: 1 → 2 (权重=8)
# 添加边: 1 → 3 (权重=-4)
# 添加边: 1 → 4 (权重=5)
# 添加边: 2 → 3 (权重=9)
# 添加边: 2 → 4 (权重=-3)
# 添加边: 3 → 4 (权重=7)
# 添加边: 4 → 1 (权重=-2)
#
# 从顶点0开始Bellman-Ford算法:
# =============================================================
#
# 第1轮松弛:
#   松弛: 0 → 1, 更新dist[1]=6
#   松弛: 0 → 2, 更新dist[2]=7
#   松弛: 1 → 3, 更新dist[3]=2
#   松弛: 1 → 4, 更新dist[4]=11
#   松弛: 2 → 4, 更新dist[4]=4
#   松弛: 4 → 1, 更新dist[1]=2
#
# 第2轮松弛:
#   松弛: 1 → 3, 更新dist[3]=-2
#
# 第3轮松弛:
#   无更新,提前结束
#
# 检测负权环:
#   无负权环 ✓
#
# 最终距离:
#   0 → 0: 0
#   0 → 1: 2
#   0 → 2: 7
#   0 → 3: -2
#   0 → 4: 4

Bellman-Ford vs Dijkstra

特性 Bellman-Ford Dijkstra
负权边 ✅ 可以 ❌ 不可以
负权环检测 ✅ 可以 ❌ 不可以
时间复杂度 O(VE) O((V+E)log V)
实际应用 稀疏图、有负权 非负权图 ⭐

算法3:Floyd-Warshall算法

核心思想:动态规划,考虑所有中间顶点

适用场景: - 求所有顶点对之间的最短路径 - 可以有负权边(但不能有负权环)

Floyd-Warshall实现

Python
class FloydWarshallGraph:
    """Floyd-Warshall算法实现"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        # 初始化距离矩阵
        self.dist = [[float('inf')] * num_vertices for _ in range(num_vertices)]
        for i in range(num_vertices):
            self.dist[i][i] = 0
        self.next = [[-1] * num_vertices for _ in range(num_vertices)]  # 负索引:从末尾倒数访问元素

    def add_edge(self, i, j, weight):
        """添加边"""
        self.dist[i][j] = weight
        self.next[i][j] = j
        print(f"添加边: {i}{j} (权重={weight})")

    def floyd_warshall(self):
        """
        Floyd-Warshall算法
        时间:O(V³)
        空间:O(V²)
        """
        print(f"\nFloyd-Warshall算法:")
        print("=" * 60)

        # 三重循环:考虑每个顶点作为中间点
        for k in range(self.num_vertices):
            print(f"\n考虑顶点{k}作为中间点:")
            for i in range(self.num_vertices):
                for j in range(self.num_vertices):
                    # 通过k的路径是否更短
                    if self.dist[i][k] + self.dist[k][j] < self.dist[i][j]:
                        self.dist[i][j] = self.dist[i][k] + self.dist[k][j]
                        self.next[i][j] = self.next[i][k]
                        print(f"  更新: {i}{k}{j} (新距离={self.dist[i][j]})")

        # 检测负权环
        print("\n检测负权环:")
        for i in range(self.num_vertices):
            if self.dist[i][i] < 0:
                print(f"  发现负权环!顶点{i}")
                return False

        print("  无负权环 ✓")
        return True

    def print_distance_matrix(self):
        """打印距离矩阵"""
        print("\n距离矩阵:")
        print("     ", end="")
        for j in range(self.num_vertices):
            print(f"{j:6}", end="")
        print()

        for i in range(self.num_vertices):
            print(f"{i:2} ", end="")
            for j in range(self.num_vertices):
                val = self.dist[i][j]
                if val == float('inf'):
                    print(f"  INF ", end="")
                else:
                    print(f"{val:6}", end="")
            print()

    def reconstruct_path(self, i, j):
        """重建路径"""
        if self.next[i][j] == -1:
            return None

        path = []
        while i != j:
            path.append(i)
            i = self.next[i][j]
        path.append(j)

        return path

# 测试
print("\n" + "=" * 60)
print("Floyd-Warshall算法测试")
print("=" * 60)

graph = FloydWarshallGraph(4)

# 构建图
graph.add_edge(0, 1, 3)
graph.add_edge(0, 2, 6)
graph.add_edge(1, 2, 2)
graph.add_edge(1, 3, 7)
graph.add_edge(2, 3, 1)

success = graph.floyd_warshall()

if success:
    graph.print_distance_matrix()

    # 示例:重建路径
    print("\n示例路径:")
    path = graph.reconstruct_path(0, 3)
    if path:
        print(f"  0 → 3: {' → '.join(map(str, path))}, 距离={graph.dist[0][3]}")

# 输出:
# ============================================================
# Floyd-Warshall算法测试
# ============================================================
# 添加边: 0 → 1 (权重=3)
# 添加边: 0 → 2 (权重=6)
# 添加边: 1 → 2 (权重=2)
# 添加边: 1 → 3 (权重=7)
# 添加边: 2 → 3 (权重=1)
#
# Floyd-Warshall算法:
# =============================================================
#
# 考虑顶点0作为中间点:
#   (无更新)
#
# 考虑顶点1作为中间点:
#   更新: 0 → 1 → 2 (新距离=5)
#   更新: 0 → 1 → 3 (新距离=10)
#
# 考虑顶点2作为中间点:
#   更新: 0 → 2 → 3 (新距离=6)
#   更新: 1 → 2 → 3 (新距离=3)
#
# 考虑顶点3作为中间点:
#
# 检测负权环:
#   无负权环 ✓
#
# 距离矩阵:
#          0      1      2      3
#  0      0      3      5      6
#  1    INF      0      2      3
#  2    INF    INF      0      1
#  3    INF    INF    INF      0
#
# 示例路径:
#   0 → 3: 0 → 1 → 2 → 3, 距离=6

最短路径算法选择

Text Only
判断流程:

1. 边权是否非负?
   是 → Dijkstra ⭐
   否 → 继续

2. 是否有负权环?
   有 → 无解(负无穷)
   否 → Bellman-Ford

3. 需要所有顶点对?
   是 → Floyd-Warshall
   否 → 继续

4. 图是否稀疏?
   是 → Bellman-Ford
   否 → 可用SPFA

最小生成树(MST,Minimum Spanning Tree)

什么是MST?

定义:给定一个带权无向连通图,MST是包含所有顶点且总权重最小的边的子集。

性质: - 包含所有顶点 - V个顶点,V-1条边 - 无环 - 权重和最小

Text Only
示例(6顶点):

原图:
    2    3
 A----B----C
 |    |   /
4|   5|  /6
 |    | /
 D----E----F
    8    3

MST(5条边,权重最小):
    2    3
 A----B----C
 |    |
4|   5|
 |    |
 D    E----F
         3

MST包含边: A-B(2), B-C(3), E-F(3), A-D(4), B-E(5)
MST权重和 = 2 + 3 + 3 + 4 + 5 = 17

算法1:Prim算法 ⭐⭐⭐⭐

核心思想:从任意顶点开始,每次添加最近的顶点

Python
import heapq

class PrimMST:
    """Prim算法求MST"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.adj_list = [[] for _ in range(num_vertices)]

    def add_edge(self, i, j, weight):
        """添加边"""
        self.adj_list[i].append((j, weight))
        self.adj_list[j].append((i, weight))

    def prim_mst(self, start=0):
        """
        Prim算法
        时间:O((V+E)log V)
        空间:O(V)
        """
        visited = [False] * self.num_vertices
        mst_edges = []
        total_weight = 0

        # 最小堆:(权重, 当前顶点, 父顶点)
        heap = [(0, start, -1)]

        print(f"\nPrim算法(从顶点{start}开始):")
        print("=" * 60)

        while heap and len(mst_edges) < self.num_vertices - 1:
            weight, vertex, parent = heapq.heappop(heap)

            if visited[vertex]:
                continue

            visited[vertex] = True

            if parent != -1:
                mst_edges.append((parent, vertex, weight))
                total_weight += weight
                print(f"添加边: {parent} ←→ {vertex} (权重={weight})")

            # 将所有未访问邻居加入堆
            for neighbor, edge_weight in self.adj_list[vertex]:
                if not visited[neighbor]:
                    heapq.heappush(heap, (edge_weight, neighbor, vertex))

        print(f"\nMST总权重: {total_weight}")
        return mst_edges, total_weight

# 测试
print("=" * 60)
print("Prim算法测试")
print("=" * 60)

graph = PrimMST(6)

# 构建示例图
edges = [
    (0, 1, 2), (0, 3, 4),
    (1, 2, 3), (1, 3, 5), (1, 4, 1),
    (2, 4, 6),
    (3, 4, 8), (3, 5, 7),
    (4, 5, 9)
]

for i, j, w in edges:
    graph.add_edge(i, j, w)

mst, weight = graph.prim_mst(0)

print("\nMST边:")
for u, v, w in mst:
    print(f"  {u} ←→ {v}: {w}")

# 输出:
# ============================================================
# Prim算法测试
# ============================================================
#
# Prim算法(从顶点0开始):
# =============================================================
# 添加边: 0 ←→ 1 (权重=2)
# 添加边: 1 ←→ 4 (权重=1)
# 添加边: 1 ←→ 2 (权重=3)
# 添加边: 0 ←→ 3 (权重=4)
# 添加边: 3 ←→ 5 (权重=7)
#
# MST总权重: 17
#
# MST边:
#   0 ←→ 1: 2
#   1 ←→ 4: 1
#   1 ←→ 2: 3
#   0 ←→ 3: 4
#   3 ←→ 5: 7

算法2:Kruskal算法 ⭐⭐⭐⭐

核心思想:按权重从小到大选边,避免形成环

Python
class UnionFind:
    """并查集(用于Kruskal算法)"""

    def __init__(self, n):
        self.parent = list(range(n))
        self.rank = [0] * n

    def find(self, x):
        """查找根节点(路径压缩)"""
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(self, x, y):
        """合并两个集合"""
        root_x = self.find(x)
        root_y = self.find(y)

        if root_x == root_y:
            return False  # 已经在同一集合

        # 按秩合并
        if self.rank[root_x] < self.rank[root_y]:
            self.parent[root_x] = root_y
        elif self.rank[root_x] > self.rank[root_y]:
            self.parent[root_y] = root_x
        else:
            self.parent[root_y] = root_x
            self.rank[root_x] += 1

        return True

class KruskalMST:
    """Kruskal算法求MST"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.edges = []

    def add_edge(self, i, j, weight):
        """添加边"""
        self.edges.append((weight, i, j))

    def kruskal_mst(self):
        """
        Kruskal算法
        时间:O(E log E)
        空间:O(V)
        """
        # 按权重排序
        self.edges.sort()

        uf = UnionFind(self.num_vertices)
        mst_edges = []
        total_weight = 0

        print("\nKruskal算法:")
        print("=" * 60)

        for weight, u, v in self.edges:
            if uf.union(u, v):
                mst_edges.append((u, v, weight))
                total_weight += weight
                print(f"添加边: {u} ←→ {v} (权重={weight})")

                if len(mst_edges) == self.num_vertices - 1:
                    break

        print(f"\nMST总权重: {total_weight}")
        return mst_edges, total_weight

# 测试(使用相同的图)
print("\n" + "=" * 60)
print("Kruskal算法测试")
print("=" * 60)

graph = KruskalMST(6)

for i, j, w in edges:
    graph.add_edge(i, j, w)

mst, weight = graph.kruskal_mst()

print("\nMST边:")
for u, v, w in mst:
    print(f"  {u} ←→ {v}: {w}")

# 输出:
# ============================================================
# Kruskal算法测试
# ============================================================
#
# Kruskal算法:
# =============================================================
# 添加边: 1 ←→ 4 (权重=1)
# 添加边: 0 ←→ 1 (权重=2)
# 添加边: 1 ←→ 2 (权重=3)
# 添加边: 0 ←→ 3 (权重=4)
# 添加边: 3 ←→ 5 (权重=7)
#
# MST总权重: 17
#
# MST边:
#   1 ←→ 4: 1
#   0 ←→ 1: 2
#   1 ←→ 2: 3
#   0 ←→ 3: 4
#   3 ←→ 5: 7

Prim vs Kruskal

特性 Prim Kruskal
策略 顶点增长 边增长
数据结构 优先队列 并查集
时间复杂度 O((V+E)log V) O(E log E)
适用场景 稠密图 ⭐ 稀疏图 ⭐
实现难度 较简单 需要并查集

拓扑排序

什么是拓扑排序?

定义:将有向无环图(DAG)的所有顶点排成线性序列,使得所有边都指向同一方向。

应用: - 任务调度(课程安排、编译依赖) - 项目管理(任务优先级) - 数据库事务排序

Text Only
示例:

课程依赖关系:    拓扑排序结果:

   A             A, B, C, D, E, F
  / \
 B   C           或
 |   |           B, A, C, D, E, F
 D   E
  \ /
   F

约束:B在D之前,C在E之前,D和E在F之前

Kahn算法(BFS)

Python
from collections import deque

class TopologicalSort:
    """拓扑排序"""

    def __init__(self, num_vertices):
        self.num_vertices = num_vertices
        self.adj_list = [[] for _ in range(num_vertices)]
        self.in_degree = [0] * num_vertices

    def add_edge(self, i, j):
        """添加边(i → j)"""
        self.adj_list[i].append(j)
        self.in_degree[j] += 1

    def kahn_topological_sort(self):
        """
        Kahn算法(BFS)
        时间:O(V+E)
        """
        queue = deque()
        result = []

        # 将所有入度为0的顶点加入队列
        for v in range(self.num_vertices):
            if self.in_degree[v] == 0:
                queue.append(v)

        print("Kahn算法(BFS)拓扑排序:")
        print("=" * 60)
        print(f"初始入度为0的顶点: {list(queue)}")

        while queue:
            vertex = queue.popleft()
            result.append(vertex)
            print(f"\n处理顶点: {vertex}")

            # 将所有邻居的入度减1
            for neighbor in self.adj_list[vertex]:
                self.in_degree[neighbor] -= 1
                print(f"  更新顶点{neighbor}的入度: {self.in_degree[neighbor] + 1}{self.in_degree[neighbor]}")

                if self.in_degree[neighbor] == 0:
                    queue.append(neighbor)
                    print(f"  顶点{neighbor}入度为0,加入队列")

        if len(result) != self.num_vertices:
            print("\n存在环!拓扑排序失败")
            return None

        print(f"\n拓扑排序结果: {result}")
        return result

# 测试
print("=" * 60)
print("拓扑排序测试")
print("=" * 60)

graph = TopologicalSort(6)

# 课程依赖:A→B, A→C, B→D, C→E, D→F, E→F
edges = [(0, 1), (0, 2), (1, 3), (2, 4), (3, 5), (4, 5)]
for i, j in edges:
    graph.add_edge(i, j)

result = graph.kahn_topological_sort()

# 输出:
# ============================================================
# 拓扑排序测试
# ============================================================
# Kahn算法(BFS)拓扑排序:
# =============================================================
# 初始入度为0的顶点: [0]
#
# 处理顶点: 0
#   更新顶点1的入度: 1 → 0
#   顶点1入度为0,加入队列
#   更新顶点2的入度: 1 → 0
#   顶点2入度为0,加入队列
#
# 处理顶点: 1
#   更新顶点3的入度: 1 → 0
#   顶点3入度为0,加入队列
#
# 处理顶点: 2
#   更新顶点4的入度: 1 → 0
#   顶点4入度为0,加入队列
#
# 处理顶点: 3
#   更新顶点5的入度: 2 → 1
#
# 处理顶点: 4
#   更新顶点5的入度: 1 → 0
#   顶点5入度为0,加入队列
#
# 处理顶点: 5
#
# 拓扑排序结果: [0, 1, 2, 3, 4, 5]

经典面试题

题目1:课程表 ⭐⭐⭐

问题:共有n门课程,记为0到n-1。给定课程总数和先修课程列表,判断是否可能完成所有课程。

示例

Text Only
输入:numCourses = 2, prerequisites = [[1,0]]
解释:总共有2门课程,学习课程1之前需要完成课程0
输出:true

解法:拓扑排序

Python
def can_finish(num_courses, prerequisites):
    """
    课程表(拓扑排序)
    时间:O(V+E)
    空间:O(V+E)
    """
    from collections import deque

    # 构建图和入度数组
    adj_list = [[] for _ in range(num_courses)]
    in_degree = [0] * num_courses

    for course, prereq in prerequisites:
        adj_list[prereq].append(course)
        in_degree[course] += 1

    # BFS
    queue = deque([c for c in range(num_courses) if in_degree[c] == 0])
    count = 0

    while queue:
        course = queue.popleft()
        count += 1

        for neighbor in adj_list[course]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)

    return count == num_courses

题目2:网络延迟时间 ⭐⭐⭐⭐

问题:有N个网络节点,标记为1到N。给定times列表(信号传递时间),求从节点K发出信号到所有节点收到信号所需时间。

解法:Dijkstra算法

Python
def network_delay_time(times, n, k):
    """
    网络延迟时间(Dijkstra)
    时间:O((V+E)log V)
    """
    import heapq

    # 构建图
    adj_list = [[] for _ in range(n + 1)]
    for u, v, w in times:
        adj_list[u].append((v, w))

    # Dijkstra
    dist = [float('inf')] * (n + 1)
    dist[k] = 0
    heap = [(0, k)]

    while heap:
        d, node = heapq.heappop(heap)

        if d > dist[node]:
            continue

        for neighbor, weight in adj_list[node]:
            if d + weight < dist[neighbor]:
                dist[neighbor] = d + weight
                heapq.heappush(heap, (dist[neighbor], neighbor))

    max_dist = max(dist[1:])  # 切片操作:[start:end:step]提取子序列
    return max_dist if max_dist != float('inf') else -1

📝 总结

图算法核心要点

图表示: - 邻接矩阵:O(V²),适合稠密图 - 邻接表:O(V+E),适合稀疏图 ⭐

图遍历: - DFS:栈,深度优先 - BFS:队列,广度优先,最短路径

最短路径: - Dijkstra:非负权,O((V+E)log V) ⭐ - Bellman-Ford:可负权,O(VE) - Floyd-Warshall:所有顶点对,O(V³)

MST: - Prim:稠密图 ⭐ - Kruskal:稀疏图 ⭐

拓扑排序: - DAG任务调度 - Kahn算法(BFS)

下一步

继续学习: - 贪心算法 - 贪心思想 - 字符串算法 - KMP、BM算法


继续查看贪心算法...