📌  相关文章
📜  通过连接成本至少为 0 的任何顶点对来最小化连接图的成本(1)

📅  最后修改于: 2023-12-03 15:12:26.232000             🧑  作者: Mango

最小生成树算法

最小生成树算法是一类在带权连通图中寻找一棵权值最小的生成树的算法。其中,通过连接成本至少为0的任何顶点对来最小化连接图的成本。

Prim 算法

Prim算法从单个节点开始,在每一步中,它将一个新节点加入到树中,这个新节点需要连接到已经树中的一个节点,被加入的节点是可以从树中已有节点到达的“最近”的节点。

算法流程
  1. 选定一个初始节点,并将其加入到生成树中。
  2. 从所有未加入生成树的节点中找到到已加入生成树的边权值最小的节点,并将其加入到生成树中。
  3. 重复第二步直到所有的节点都加入了生成树中。
代码实现
from queue import PriorityQueue

def prim(graph, start):
    """
    Prim算法实现最小生成树

    :param graph: 图
        {v1: {u1: w1, u2: w2, ...}, v2: {...}, ...}
    :param start: 起点
    :return: 生成树
        {(v1, u1, w1), (v2, u2, w2), ...}
    """
    # 初始化生成树集合
    mst = set()
    # 初始化已访问的节点集合
    visited = set(start)
    # 初始化候选边的队列
    candidates = PriorityQueue()
    # 将起点的所有出边加入候选边队列
    for v, w in graph[start].items():
        candidates.put((w, start, v))
    # 循环直到生成树集合中包含所有节点或所有候选边遍历完
    while candidates and len(visited) < len(graph):
        # 取出队列中距离起点最近的边
        w, u, v = candidates.get()
        # 如果v已被访问过,说明这条边形成环,抛弃它
        if v in visited:
            continue
        # 将这条边添加进生成树
        mst.add((u, v, w))
        # 将v加入到已访问的节点集合中
        visited.add(v)
        # 将v的所有出边加入候选边队列中
        for next_v, next_w in graph[v].items():
            if next_v not in visited:
                candidates.put((next_w, v, next_v))
    return mst
示例
graph = {
    'A': {'B': 2, 'C': 3},
    'B': {'A': 2, 'C': 4, 'D': 3},
    'C': {'A': 3, 'B': 4, 'D': 5},
    'D': {'B': 3, 'C': 5},
}
mst = prim(graph, 'A')
print(mst)
# Output: {('A', 'B', 2), ('B', 'D', 3), ('A', 'C', 3)}
Kruskal算法

Kruskal算法是一种基于贪心算法的最小生成树算法,通过不断地选择边来将图中的点连接起来,但是在选择的过程中会过滤掉那些会形成环的边。

算法流程
  1. 先将所有的边按照权重从小到大排序。
  2. 将所有的点看做是孤立的点。
  3. 遍历排序后的边,如果这条边将两个孤立点连接,就加入到最小生成树中。
  4. 如果加入后形成环,就不加入。
代码实现
def kruskal(graph):
    """
    Kruskal算法实现最小生成树

    :param graph: 图
        [(v1, u1, w1), (v2, u2, w2), ...]
    :return: 生成树
        [(v1, u1, w1), (v2, u2, w2), ...]
    """
    # 初始化生成树集合
    mst = []
    # 初始化并查集
    parent = {}
    def find(x):
        if x != parent[x]:
            parent[x] = find(parent[x])
        return parent[x]
    def union(x, y):
        parent[find(x)] = find(y)
    for u, v, w in graph:
        # 初始化并查集
        parent.setdefault(u, u)
        parent.setdefault(v, v)
        # 如果u,v的根节点不同,说明这条边不会形成环,可以加入生成树
        if find(u) != find(v):
            mst.append((u, v, w))
            union(u, v)
    return mst
示例
graph = [
    ('A', 'B', 2),
    ('A', 'C', 3),
    ('B', 'C', 4),
    ('B', 'D', 3),
    ('C', 'D', 5),
]
mst = kruskal(graph)
print(mst)
# Output: [('A', 'B', 2), ('A', 'C', 3), ('B', 'D', 3)]
总结

Prim算法使用的是节点的概念,每次从已经生成的节点出发,选择一条权值最小的边,并将其与一个新的节点连接。Kruskal算法使用的是边的概念,每次从剩下的边中选择一条最小的边,并将其与已经选择的边进行比较,如果不会形成环,就选择这条边。两种算法均能得到最小生成树。