📜  求解最小生成树(Kruskal 和 Prim)的问题(1)

📅  最后修改于: 2023-12-03 14:56:00.314000             🧑  作者: Mango

概述

最小生成树是指在一张加权无向图中,包含所有顶点的生成树中,边的权值之和最小的一棵生成树。Kruskal 和 Prim 算法是两种常用的求解最小生成树的算法。

本文将介绍 Kruskal 和 Prim 算法的基本思路和实现步骤,并给出 Python 代码实现。

Kruskal 算法

Kruskal 算法是一种贪心算法,其基本思路是:

  1. 将所有边按权值从小到大进行排序;
  2. 依次选择排好序的边加入生成树中,如果新加入的边会形成环,则不加入该边,直到所有的点都加入到生成树中。

Kruskal 算法的实现步骤如下:

  1. 将所有边按边权排序;
  2. 创建一个并查集,并查集中每个点单独为一个连通分量;
  3. 依次选取每条边,如果该边连接的两个端点不在同一个连通分量中,则将这两个连通分量合并成一个;
  4. 如果加入的边数等于顶点总数减一,则停止;否则重复步骤 3。

以下是 Kruskal 算法的 Python 代码实现:

def kruskal(graph):
    parent = {}
    rank = {}
    result = []
    edges = sorted(graph.edges.items(), key=lambda x: x[1])  # 按边权从小到大排序
    for node in graph.nodes:
        parent[node] = node
        rank[node] = 0
    def find(node):
        if parent[node] != node:
            parent[node] = find(parent[node])
        return parent[node]
    def union(node1, node2):
        root1, root2 = find(node1), find(node2)
        if root1 != root2:
            if rank[root1] > rank[root2]:
                parent[root2] = root1
            else:
                parent[root1] = root2
                if rank[root1] == rank[root2]:
                    rank[root2] += 1
            return True
        else:
            return False
    for edge in edges:
        node1, node2 = edge[0]
        if union(node1, node2):
            result.append((node1, node2))
        if len(result) == len(graph.nodes) - 1:
            break
    return result

其中,graph 是一个图对象,包括两个属性:nodes 表示节点集合,edges 表示边和边权的字典,例如:

graph = {
    "nodes": ["A", "B", "C", "D", "E", "F"],
    "edges": {
        ("A", "B"): 1, ("A", "C"): 4, ("B", "C"): 2,
        ("B", "D"): 5, ("C", "D"): 3, ("C", "E"): 6,
        ("D", "E"): 7, ("D", "F"): 8, ("E", "F"): 9
    }
}

Prim 算法

Prim 算法也是一种贪心算法,其基本思路是:

  1. 首先任选一个节点作为起点,将其加入集合 U 中;
  2. 对于 U 中的每个节点,选择一条连接到 V-U(即 V 中不属于 U 的节点)中权值最小的边,将其加入生成树中,同时将其连接的节点加入 U 中;
  3. 重复步骤 2,直到 U 包含了所有的节点。

Prim 算法的实现步骤如下:

  1. 创建一个空的集合 U 和一个字典 key,其中 key 记录节点到 U 中最小边的边权,初始值均为 inf 或一个足够大的数;
  2. 任选一个节点作为起点,将其加入 U 中;
  3. 对于 U 中的每个节点,选择一条连接到 V-U 中边权最小的边,将其连接的节点加入 U 中,更新 key 的值;
  4. 重复步骤 3,直到 U 包含了所有的节点。

以下是 Prim 算法的 Python 代码实现:

from heapq import heappush, heappop

def prim(graph):
    parent = {}
    key = {}
    heap = []
    result = []
    for node in graph.nodes:
        key[node] = float("inf")
    start_node = graph.nodes[0]
    key[start_node] = 0
    heappush(heap, (key[start_node], start_node))
    while heap:
        weight, node = heappop(heap)
        if node not in parent:
            parent[node] = None
            for neighbour in graph.edges[node]:
                if neighbour not in parent and graph.edges[node][neighbour] < key[neighbour]:
                    key[neighbour] = graph.edges[node][neighbour]
                    heappush(heap, (key[neighbour], neighbour))
            if parent[node]:
                result.append((parent[node], node))
    return result

其中,graph 是一个图对象,包括两个属性:nodes 表示节点集合,edges 表示边和边权的字典,例如:

graph = {
    "nodes": ["A", "B", "C", "D", "E", "F"],
    "edges": {
        "A": {"B": 1, "C": 4},
        "B": {"A": 1, "C": 2, "D": 5},
        "C": {"A": 4, "B": 2, "D": 3, "E": 6},
        "D": {"B": 5, "C": 3, "E": 7, "F": 8},
        "E": {"C": 6, "D": 7, "F": 9},
        "F": {"D": 8, "E": 9}
    }
}

总结

Kruskal 算法和 Prim 算法都是求解最小生成树的经典算法,它们的时间复杂度均为 O(ElogE),其中 E 表示边数。两个算法的主要区别在于,Kruskal 算法是基于边的操作,Prim 算法是基于节点的操作。在实际应用中,我们可以根据具体的问题选择适合的算法。