图算法基础教程

前置知识

建议先了解基本的图概念(顶点、边、度等)和数据结构(队列、栈)。本文预计阅读时间 20 分钟。

一、什么是图? 🌐

图(Graph)是一种由顶点(Vertex)和(Edge)组成的数据结构,用于表示对象之间的关系。

图的类型

无向图
边没有方向,如社交网络中的好友关系。
有向图
边有方向,如网页之间的链接关系。
加权图
边有权重,如地图中城市间的距离。

图的应用场景

二、图的表示方法 📊

在实现图算法之前,我们需要选择合适的图的表示方式。

方法一:邻接矩阵

邻接矩阵实现
class GraphMatrix:
    def __init__(self, vertices):
        self.V = vertices
        # 初始化邻接矩阵,0 表示无边
        self.graph = [[0] * vertices for _ in range(vertices)]
    
    def add_edge(self, u, v, weight=1):
        """添加边(无向图)"""
        self.graph[u][v] = weight
        self.graph[v][u] = weight
    
    def display(self):
        """显示邻接矩阵"""
        for row in self.graph:
            print(row)

# 使用示例
g = GraphMatrix(4)
g.add_edge(0, 1, 5)
g.add_edge(0, 2, 3)
g.add_edge(1, 3, 2)
g.display()

优点

缺点

方法二:邻接表

邻接表实现
from collections import defaultdict

class GraphList:
    def __init__(self):
        # 使用字典存储邻接表
        self.graph = defaultdict(list)
    
    def add_edge(self, u, v, weight=1):
        """添加边(无向图)"""
        self.graph[u].append((v, weight))
        self.graph[v].append((u, weight))
    
    def display(self):
        """显示邻接表"""
        for vertex in self.graph:
            print(f"{vertex}: {self.graph[vertex]}")

# 使用示例
g = GraphList()
g.add_edge(0, 1, 5)
g.add_edge(0, 2, 3)
g.add_edge(1, 3, 2)
g.display()

优点

缺点

选择建议

三、图的遍历算法 🔍

图遍历是许多图算法的基础,主要有两种方法:深度优先搜索(DFS)和广度优先搜索(BFS)。

1. 深度优先搜索(DFS)

DFS 类似于树的先序遍历,尽可能深地搜索图的分支。

DFS 实现(递归)
def dfs_recursive(graph, vertex, visited=None):
    """
    深度优先搜索(递归版本)
    :param graph: 邻接表表示的图
    :param vertex: 当前顶点
    :param visited: 已访问集合
    """
    if visited is None:
        visited = set()
    
    visited.add(vertex)
    print(vertex, end=' ')  # 处理当前节点
    
    # 递归访问所有未访问的邻居
    for neighbor, weight in graph[vertex]:
        if neighbor not in visited:
            dfs_recursive(graph, neighbor, visited)

# 测试
graph = {
    0: [(1, 1), (2, 1)],
    1: [(0, 1), (3, 1)],
    2: [(0, 1), (3, 1)],
    3: [(1, 1), (2, 1)]
}
print("DFS 遍历结果:")
dfs_recursive(graph, 0)
# 输出: 0 1 3 2

2. 广度优先搜索(BFS)

BFS 类似于树的层序遍历,逐层扩展搜索范围。

BFS 实现(迭代)
from collections import deque

def bfs(graph, start):
    """
    广度优先搜索
    :param graph: 邻接表表示的图
    :param start: 起始顶点
    """
    visited = set()
    queue = deque([start])
    visited.add(start)
    
    while queue:
        vertex = queue.popleft()
        print(vertex, end=' ')  # 处理当前节点
        
        # 将所有未访问的邻居加入队列
        for neighbor, weight in graph[vertex]:
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append(neighbor)

# 测试
print("\nBFS 遍历结果:")
bfs(graph, 0)
# 输出: 0 1 2 3

DFS vs BFS 对比

特性 DFS BFS
数据结构 栈(递归或显式) 队列
空间复杂度 O(V) O(V)
适用场景 连通性检测、拓扑排序 最短路径(无权图)
遍历顺序 深度优先 广度优先

注意事项

四、最短路径算法 🛣️

最短路径问题是图算法中最经典的问题之一。

1. Dijkstra 算法

Dijkstra 算法用于求解加权图中单源最短路径,要求权重非负。

Dijkstra 算法实现
import heapq

def dijkstra(graph, start):
    """
    Dijkstra 最短路径算法
    :param graph: 邻接表 {节点: [(邻居, 权重), ...]}
    :param start: 起始节点
    :return: 到各节点的最短距离
    """
    # 初始化距离
    dist = {node: float('inf') for node in graph}
    dist[start] = 0
    
    # 优先队列:(距离, 节点)
    pq = [(0, start)]
    visited = set()
    
    while pq:
        current_dist, u = heapq.heappop(pq)
        
        # 跳过已处理的节点
        if u in visited:
            continue
        visited.add(u)
        
        # 松弛操作
        for v, weight in graph[u]:
            distance = current_dist + weight
            
            # 如果找到更短的路径
            if distance < dist[v]:
                dist[v] = distance
                heapq.heappush(pq, (distance, v))
    
    return dist

# 测试
graph = {
    'A': [('B', 1), ('C', 4)],
    'B': [('A', 1), ('C', 2), ('D', 5)],
    'C': [('A', 4), ('B', 2), ('D', 1)],
    'D': [('B', 5), ('C', 1)]
}

distances = dijkstra(graph, 'A')
print("从 A 出发的最短距离:")
for node, dist in distances.items():
    print(f"A -> {node}: {dist}")

时间复杂度:O((V + E) log V),使用优先队列优化

2. Bellman-Ford 算法

Bellman-Ford 算法可以处理负权重的边,并能检测负权环。

python
def bellman_ford(graph, vertices, start):
    """
    Bellman-Ford 算法
    :param graph: 边列表 [(u, v, weight), ...]
    :param vertices: 顶点数量
    :param start: 起始节点
    :return: 最短距离字典
    """
    # 初始化
    dist = {i: float('inf') for i in range(vertices)}
    dist[start] = 0
    
    # 松弛所有边 V-1 次
    for _ in range(vertices - 1):
        for u, v, weight in graph:
            if dist[u] != float('inf') and dist[u] + weight < dist[v]:
                dist[v] = dist[u] + weight
    
    # 检测负权环
    for u, v, weight in graph:
        if dist[u] != float('inf') and dist[u] + weight < dist[v]:
            return None  # 存在负权环
    
    return dist

# 测试
edges = [
    (0, 1, 4),
    (0, 2, 5),
    (1, 2, -3),
    (2, 3, 4)
]
result = bellman_ford(edges, 4, 0)
if result:
    print("最短距离:", result)
else:
    print("图中存在负权环")

五、最小生成树(MST) 🌳

最小生成树是连接所有顶点的边的子集,且总权重最小。

Kruskal 算法

Kruskal 算法通过贪心策略构建最小生成树。

Kruskal 算法
class UnionFind:
    """并查集数据结构"""
    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, root_y = self.find(x), 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

def kruskal(vertices, edges):
    """
    Kruskal 最小生成树算法
    :param vertices: 顶点数量
    :param edges: 边列表 [(u, v, weight), ...]
    :return: MST 的边列表和总权重
    """
    # 按权重排序
    edges.sort(key=lambda x: x[2])
    
    uf = UnionFind(vertices)
    mst = []
    total_weight = 0
    
    for u, v, weight in edges:
        if uf.union(u, v):  # 如果不形成环
            mst.append((u, v, weight))
            total_weight += weight
            
            # MST 完成
            if len(mst) == vertices - 1:
                break
    
    return mst, total_weight

# 测试
vertices = 4
edges = [
    (0, 1, 10),
    (0, 2, 6),
    (0, 3, 5),
    (1, 3, 15),
    (2, 3, 4)
]

mst, weight = kruskal(vertices, edges)
print("最小生成树的边:")
for u, v, w in mst:
    print(f"{u} - {v}: {w}")
print(f"总权重: {weight}")

六、实际应用案例 💼

案例一:社交网络分析

Facebook 使用图算法来分析用户关系网络:

PageRank 算法简介

PageRank 是 Google 搜索引擎的核心算法,也可用于社交网络。

基本思想:

  • 一个页面的重要性取决于指向它的其他页面的重要性
  • 迭代计算直到收敛
  • 时间复杂度:O(k * E),k 为迭代次数

案例二:GPS 导航系统

现代导航系统结合多种图算法:

案例三:网络路由

互联网路由器使用图算法决定数据包的最佳路径:

七、学习建议与练习 📚

推荐练习题

LeetCode 200. 岛屿数量
给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算岛屿的数量。 难度:⭐⭐ 中等 涉及算法:DFS/BFS
LeetCode 743. 网络延迟时间
有 N 个网络节点,标记为 1 到 N。给定信号从一个节点传到另一个节点所需的时间,求从节点 K 发出的信号到达所有节点的最长时间。 难度:⭐⭐ 中等 涉及算法:Dijkstra

八、总结与展望 🎯

图算法是解决现实世界复杂问题的强大工具。

图遍历

DFS 和 BFS 是所有图算法的基础。

最短路径

Dijkstra 和 Bellman-Ford 解决路径优化问题。

最小生成树

Kruskal 和 Prim 算法优化网络连接。

实际应用

从社交网络到导航系统,无处不在。

探索更多图算法

图的世界非常精彩,还有最大流、二分图匹配等更多内容等待你探索!

查看高级图算法 下载完整代码

© 2026 AI未来实验室 · 用 AI 探索无限可能
站点地图 | Robots.txt | 关于