最小生成树-- Kruskal 算法

最小生成树-- Kruskal 算法

一.最小生成数介绍

最小生成树算法是一个用于带权连通图的生成树问题。最小生成树问题的目标是找到一种生成树,使得该树上所有边的权值之和最小。目前常用的两种最小生成树算法是 Kruskal 算法和 Prim 算法,它们都基于贪心思想,通过不断加入边来生成最小生成树,时间复杂度分别为 O(ElogE) 和 O(ElogV)。而本文首先介绍Kruskal这一常用算法,预计下篇出Prim 算法。最小生成树算法在实际应用中可以用于通信网络规划、电力传输、城市规划、因特网路由等领域。比如采用最短线路将分布的灯泡连在一起。

在了解最小生成树之前,我们首先需要了解图的基本概念:

1.连通图:一个无向图中,如果任意两个节点之间都存在路径,那么这个图就是连通图。

2.生成树:对于一个连通图,生成树是指包含图中所有节点的一个子图,并且是一棵树(即没有环路)。

3.边的权值:在图的每条边上都有一个权值,用来表示该边的重要程度或代价。

这三点其实在本问题中体现的淋漓尽致。

二.Kruskal算法

我们介绍下最小生成数其中一个主流算法Kruskal,此算法比较Prim算法更适用于边稀疏的情况。

本算法其实非常简单,类似于最短线路,不懂最短线路可以看我上期,主要是分为四个核心步骤:

1.将图中的所有边按照权值进行排序。

2.从权值最小的边开始,依次遍历排序后的边。

3.如果这条边不会导致生成树形成环路,即加入该边不会形成闭合回路,则将它加入生成树中。

4.重复步骤3,直到生成树中的边数等于节点数减一,或者图中的边都被考虑完为止。

为了判断是否形成环路,Kruskal 算法使用并查集数据结构。每个节点在并查集中都有一个代表元素,初始时每个节点代表元素都是自己。当要判断两个节点是否属于同一个集合时,可以通过查找它们的代表元素来进行比较。如果两个节点的代表元素不同,说明它们属于不同的集合,可以将它们合并为同一个集合。排序可有内部实现。

为了防止解释不清,我们先简单了解下什么是并查集,推荐个视频【图论——并查集(详细版)】https://www.bilibili.com/video/BV1jv411a7LK?vd_source=a99a6abc406cf4968ddb5c1b363abc55

它的作用就是判断是否组成了一个环,边的加入时,高效地判断两个顶点是否在同一个集合中。通过并查集的 find 操作可以获得某个顶点所属集合的根节点,然后比较两个顶点的根节点是否相同,从而确定它们是否在同一个集合中。

三.代码实现

graph = {
    'A': {'B': 5, 'C': 2, 'D': 3, 'E': 9, 'F': 4},
    'B': {'A': 5, 'C': 1, 'E': 3},
    'C': {'A': 2, 'B': 1, 'D': 4},
    'D': {'A': 3, 'C': 4, 'E': 2},
    'E': {'A': 9, 'B': 3, 'D': 2, 'F': 6},
    'F': {'A': 4, 'E': 6}
}

#这里就定义并查集,因为Python中没有现成的
class UnionFind:
    def __init__(self):
        self.parent = {}

    def find(self, u):
        if u not in self.parent:
            self.parent[u] = u
            return u
        while self.parent[u] != u:
            u = self.parent[u]
        return u

    def union(self, p, q):
        root_p, root_q = self.find(p), self.find(q)
        self.parent[root_p] = root_q


# Kruskal 算法实现
def kruskal(g):
    edges = []
    for a in g:
        for b, w in g[a].items():
            edges.append((a, b, w))  # 将顶点和对应的权重添加到边列表中
    edges.sort(key=lambda x: x[2])  # 根据权重对边列表进行排序
    uf = UnionFind()
    mst = set()
    for a, b, w in edges:
        if uf.find(a) != uf.find(b):
            mst.add((a, b, w))  # 将选择的边添加到最小生成树中
            uf.union(a, b)
    return mst


mst = kruskal(graph)
print(mst)

{('B', 'C', 1), ('D', 'E', 2), ('A', 'C', 2), ('A', 'F', 4), ('A', 'D', 3)}

这样就将这几个点连接在一起了。

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>

using namespace std;

struct Edge {
    char src, dest;
    int weight;

    Edge(char s, char d, int w)
        : src(s), dest(d), weight(w) {}
};

class UnionFind {
public:
    unordered_map<char, char> parent;

    char find(char u) {
        if (parent.find(u) == parent.end()) {
            parent[u] = u;
            return u;
        }
        while (parent[u] != u) {
            u = parent[u];
        }
        return u;
    }

    void unite(char p, char q) {
        char root_p = find(p);
        char root_q = find(q);
        parent[root_p] = root_q;
    }
};

vector<Edge> kruskal(unordered_map<char, unordered_map<char, int>>& graph) {
    vector<Edge> edges;
    for (auto& a : graph) {
        for (auto& b : a.second) {
            edges.push_back(Edge(a.first, b.first, b.second));
        }
    }
    sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
        return a.weight < b.weight;
        });

    UnionFind uf;
    vector<Edge> mst;
    for (const auto& edge : edges) {
        if (uf.find(edge.src) != uf.find(edge.dest)) {
            mst.push_back(edge);
            uf.unite(edge.src, edge.dest);
        }
    }
    return mst;
}

int main() {
    unordered_map<char, unordered_map<char, int>> graph = {
        {'A', {{'B', 5}, {'C', 2}, {'D', 3}, {'E', 9}, {'F', 4}}},
        {'B', {{'A', 5}, {'C', 1}, {'E', 3}}},
        {'C', {{'A', 2}, {'B', 1}, {'D', 4}}},
        {'D', {{'A', 3}, {'C', 4}, {'E', 2}}},
        {'E', {{'A', 9}, {'B', 3}, {'D', 2}, {'F', 6}}},
        {'F', {{'A', 4}, {'E', 6}}}
    };

    vector<Edge> mst = kruskal(graph);
    for (const auto& edge : mst) {
        cout << edge.src << " - " << edge.dest << " : " << edge.weight << endl;
    }

    return 0;
}

C++亦是。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xin2cd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值