一、从乡村公路规划说起

想象你是一位负责偏远地区公路建设的工程师。这里有7个村庄需要连通,每两个村庄之间修路的成本不同。上级要求你用最省钱的方式,让所有村庄都能互相到达,这时候你需要的就是最小生成树算法。

什么是生成树?简单说就是用最少的道路(n-1条)把所有n个村庄连起来,并且没有环路。就像用钢筋把各个节点焊接成稳固的结构。

二、Prim算法——星火燎原

2.1 生动场景

假设你带着施工队来到A村(任选起点),现在要做两个决策:

  1. 当前已连通的村庄到未连通村庄的最短道路
  2. 沿着这条最短道路连接到新村庄

2.2 分步演示(以下图为例)

    A2B3C
    |     |     |
    4     1     6
    |     |     |
    D5E7F

步骤分解:

  1. 从A出发(已连接:{A})
  2. 找到A的最短邻接路:A-B(2)
  3. 连接B(已连接:{A,B})
  4. 找{A,B}到其他村的最短路:B-E(1)
  5. 连接E(已连接:{A,B,E})
  6. 持续这个过程直到连通所有村庄

2.3 Python实现(邻接矩阵)

def prim(graph):
    n = len(graph)
    selected = [False]*n
    min_cost = [float('inf')]*n
    min_cost[0] = 0  # 从第0个节点开始
    parent = [ -1 ]*n
    
    for _ in range(n):
        # 找当前最小成本的节点
        u = min((cost, i) for i, cost in enumerate(min_cost) 
                if not selected[i])[1]
        
        selected[u] = True
        
        # 更新邻居节点的最小成本
        for v in range(n):
            if graph[u][v] > 0 and not selected[v]:
                if graph[u][v] < min_cost[v]:
                    min_cost[v] = graph[u][v]
                    parent[v] = u
                    
    return sum(graph[v][parent[v]] for v in range(1,n))

三、Kruskal算法——拼图大师

3.1 核心思想

把修路过程想象成玩拼图:

  1. 把所有的道路按成本从小到大排序
  2. 从最便宜的开始拼接
  3. 关键原则:如果当前道路连接的两个村庄已经连通,就跳过(避免环路)

3.2 并查集——防环利器

class UnionFind:
    def __init__(self, size):
        self.parent = list(range(size))
    
    def find(self, x):
        while self.parent[x] != x:
            x = self.parent[x]
        return x
    
    def union(self, x, y):
        rootX = self.find(x)
        rootY = self.find(y)
        if rootX != rootY:
            self.parent[rootY] = rootX

3.3 算法实现

def kruskal(graph):
    edges = []
    n = len(graph)
    for u in range(n):
        for v, w in graph[u]:
            edges.append((w, u, v))
    edges.sort()
    
    uf = UnionFind(n)
    res = 0
    
    for w, u, v in edges:
        if uf.find(u) != uf.find(v):
            uf.union(u, v)
            res += w
    return res

四、两种算法对比

特性 Prim算法 Kruskal算法
适用场景 稠密图 稀疏图
时间复杂度 O(n²) O(m log m)
核心数据结构 优先队列/最小堆 并查集
选择策略 节点扩展 边排序

五、现实应用场景

  1. 电网布线:用最少的电缆连接所有城市
  2. 物流规划:建立覆盖所有网点的最低成本运输网
  3. 通信网络:设计光纤骨干网的拓扑结构
  4. 生物进化树:构建物种间的演化关系

六、常见问题解答

Q:为什么两种算法得到的结果总是一样?
A:因为最小生成树在边权不重复时是唯一的,当有权值相同的边时才可能有不同结果。

Q:如何处理不连通的图?
A:最小生成树只存在于连通图,如果图不连通,得到的将是生成森林(多个生成树)。

Q:算法中的贪心策略体现在哪里?
A:Prim每次选当前最小边,Kruskal按全局最小边顺序选择,都体现了局部最优导向全局最优的思想。