最小生成树 (MST基础)

1.引言

第一篇blog,就挑点简单的东西写吧。

有一个这样的问题。有n个城市,先要在城市之间修路,城市u与城市v之间修路的成本是 au,v a_{u,v} au,v,求让所有城市建通所需要的最短成本。这就是经典的最小生成树(MST)问题。

2.MST基础

前置概念:

生成树:包含所有顶点,无环,联通的子图;

最小生成树:边权和最小的生成树;

MST本质就是给你一个图,让你求最小生成树,这种问题我们通常用贪心的策略解决。

下面介绍两种求最小生成树的方法。

1.Kruskal算法

贪心选边,将所有边按边权从小到大排序,每次考虑能不能将边权最小的边加入树。若加入这条边后不形成环,就允许加入树。直到选出n-1条边。具体动画演示可以看这里。那下面直接给出代码

ini 复制代码
struct Edge {
    int u, v, w;
    bool operator<(const Edge& other) const {
        return w < other.w;
    }
};

vector<Edge> edges; // 存所有边
vector<int> parent, sz;

int find(int x) {
    return parent[x] == x ? x : parent[x] = find(parent[x]);
}//找祖先

bool unite(int a, int b) {
    a = find(a), b = find(b);
    if (a == b) return false;//在同一个连通块,再加边则成环了
    if (sz[a] < sz[b]) swap(a, b);
    parent[b] = a;
    sz[a] += sz[b];
    return true;
}

ll kruskal(int n) {
    sort(edges.begin(), edges.end());
    parent.resize(n+1);
    sz.assign(n+1, 1);
    for(int i=0;i<parent.size();i++){
        parent[i]=i;//父节点指向自己
    }
    ll res = 0;
    int cnt = 0;//已经加入的边的个数
    for (auto [u, v, w] : edges) {
        if (unite(u, v)) {
            res += w;
            if (++cnt == n-1) break;
        }
    }
    return res;
}

设边数m,时间复杂度O(mlogm).n为点数,m为边数,后续分析一样。 Kruskal算法是从边出发的,但是存在稠密图(即边很多的图,尤其是完全图),m极大,对Kruskal很不友好,那么就需要从点出发了。

2.Prim算法

只关注于一个联通块,选择其连出去的边权最小的边,将边和新点加入,用优先队列维护候选边。动态演示上面视频有,直接给出代码

arduino 复制代码
vector<pair<int,int>> adj[N]; // adj[u] = {v, w} 邻接表,u连到v权值w
bool vis[N];

ll prim(int n, int start = 1) {
    priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq;
    //pq按第一个数(w,权值)顺序排序
    pq.push({0, start});
    ll res = 0;
    int cnt = 0;
    while (!pq.empty()) {
        auto [w, u] = pq.top(); pq.pop();
        if (vis[u]) continue;
        vis[u] = true;
        res += w;
        if (++cnt == n) break;
        for (auto [v, nw] : adj[u]) {
            if (!vis[v]) pq.push({nw, v});
        }//目标连通块的所有未选连出边都在pq里
    }
    return res;
}

时间复杂度O(mlogn).

各位注意方法选择,稠密图用Prim会更优

3.Borůvka 算法

具体步骤如下:

初始时,每个点都是一个连通分量

重复一下步骤,直至仅剩一个连通分量:

对于每一个连通分量,找到它连向其他边的最小边权

将这些最小边权全部加到MST中

如图:

下面给出代码

struct 复制代码
    int u, v, w;
};

int find(int x, vector<int>& parent) {
    return parent[x] == x ? x : parent[x] = find(parent[x], parent);
}

bool unite(int a, int b, vector<int>& parent, vector<int>& sz) {
    a = find(a, parent), b = find(b, parent);
    if (a == b) return false;
    if (sz[a] < sz[b]) swap(a, b);
    parent[b] = a;
    sz[a] += sz[b];
    return true;
}

ll boruvka(int n, vector<Edge>& edges) {
    vector<int> parent(n + 1), sz(n + 1, 1);
    iota(parent.begin(), parent.end(), 0);

    ll res = 0;
    int components = n;

    while (components > 1) {
        // minEdge[i] 存分量 i 的最小出边 {u, v, w}
        vector<Edge> minEdge(n + 1, {0, 0, INT_MAX});

        for (auto& e : edges) {
            int u = find(e.u, parent);
            int v = find(e.v, parent);
            if (u == v) continue;               // 同分量,跳过
            if (e.w < minEdge[u].w) minEdge[u] = e;  // 更新 u 的最小出边
            if (e.w < minEdge[v].w) minEdge[v] = e;  // 更新 v 的最小出边
        }

        bool merged = false;
        for (int i = 1; i <= n; i++) {
            if (minEdge[i].w == INT_MAX) continue;  // 这个分量没有出边了
            int u = minEdge[i].u, v = minEdge[i].v, w = minEdge[i].w;
            if (unite(u, v, parent, sz)) {
                res += w;
                components--;
                merged = true;
            }
        }

        if (!merged) break;  // 没有可以合并的了(图不连通)
    }

    return components == 1 ? res : -1;  // -1 表示图不连通
}

用于可以快速找出最小边的题目,不是很常用。 3.例题 \[\](造桥与砍树 - Problem - QOJ.ac) Prim和Borůvka都可以做,快去试试吧

相关推荐
天天喝旺仔8 分钟前
Git 内部原理深度解析:从 blob/tree/commit 对象到 packfile 与垃圾回收
数据结构·数据库·git·算法·哈希
AIGCmagic社区10 分钟前
KITTI AbsRel从6.5压到5.4,Marigold V2用一张32GB卡把编辑DiT收成单步深度估计
人工智能·算法·aigc·ai多模态
青少儿编程课堂11 分钟前
多源最短路与最小环(Floyd 算法图论解析)
c++·python·算法·bfs·信息学竞赛
6Hzlia19 分钟前
【Classic 150 刷题计划】 LeetCode 228. 汇总区间 | C++ 锚点游标与断点检测法
c++·算法·leetcode
Edward The Bunny34 分钟前
Leetcode Hot 100
数据结构·算法
hans汉斯37 分钟前
数据挖掘|基于BP神经网络的少数民族村寨文化型旅游体验产品潜在游客挖掘
深度学习·神经网络·算法·yolo·软件工程·bp·汉斯出版社
v_348360876210 小时前
Android native端 堆栈打印
android·算法
residual_fan10 小时前
【学术论文】航空发动机故障诊断智能体:基于持续对比强化学习的动态优化方法
人工智能·算法·数据挖掘·数据分析
GreenTea11 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
深圳满意度咨询12 小时前
医院满意度数字化测评系统:从数据采集到智能决策的技术实践
算法