码迷,mamicode.com
首页 > 其他好文 > 详细

最小生成树两连

时间:2020-04-07 12:27:31      阅读:34      评论:0      收藏:0      [点我收藏+]

标签:bre   起点   按秩合并   选中   code   适用于   开始   printf   数据结构   

最小生成树两连

并查集优化的克鲁斯卡尔算法和优先队列+链式前向星优化的普利姆算法

Kruskal

Kruskal是常用的最小生成树算法,算法利用贪心思想,每次选择没用过且不构成环的边的最小边,直到选择了n-1条边,通常我们用并查集这个数据结构去优化,优化后的Kruskal算法复杂度是\(O(mlogm+m\alpha m)\),复杂度只和边的数量有关,所以适用于稀疏图

#include <bits/stdc++.h>
using namespace std;
int n;                     // n个点
int m;                     // m条边
int ans;                   //最小生成树的权值
const int MAXM = 2e5 + 5;  //边的数据范围
const int MAXN = 5005;     //点的数据范围

struct node {
    int u;
    int v;
    int w;
} e[MAXM];

bool cmp(node a, node b) { return a.w < b.w; }
int parent[MAXN];
int rnk[MAXN];
void init() {
    ans = 0;
    for (int i = 0; i <= n; i++) {
        rnk[i] = 0;
        parent[i] = i;
    }
}
int find(int x) {
    if (parent[x] == x) {
        return x;
    } else {
        return parent[x] = find(parent[x]);  //路径压缩
    }
}
void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (x == y) {
        return;
    }
    if (rnk[x] < rnk[y]) {  //按秩合并
        parent[x] = y;
    } else {
        parent[y] = x;
        if (rnk[x] == rnk[y]) {
            rnk[x]++;
        }
    }
}
bool issame(int x, int y) { return find(x) == find(y); }
void kruskal(void) {
    ans = 0;
    int cnt = 0;                    // 记录已经选用的边数
    for (int i = 1; i <= m; i++) {  //枚举m条边
        //判断是否在一个集合中
        if (!issame(e[i].u, e[i].v)) {
            //没在集合中,就选中这条边,把u,v两点连起来
            unite(e[i].u, e[i].v);
            ans += e[i].w;
            cnt++;
        }
        if (cnt == n - 1) {  //选到了n-1条边了
            break;
        }
    }
}
int main(void) {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
    }
    sort(e + 1, e + m + 1, cmp);  //按照边的权值排序
    init();
    kruskal();
    printf("%d\n", ans);
    return 0;
}

Prim

Prim是针对点的最小生成树算法,任意选一个点出发,构成一个集合V,然后每次选择距离这个集合最近的点加入,当所有点都被加入集合V中时得到这棵树。使用优先队列和链式前向星优化的Prim算法的时间复杂度是\(O((m+n)logm)\),适用于稠密图

#include <bits/stdc++.h>
using namespace std;
int n;                     // n个点
int m;                     // m条边
int ans;                   //最小生成树的权值
int cnt;                   //边的index
const int MAXM = 2e5 + 5;  //边的数据范围
const int MAXN = 5005;     //点的数据范围

struct node {
    int from;  //和这条边同起点的上一条边的存储位置
    int to;    //这条边的终点
    int w;
    friend bool operator<(node a, node b) { return a.w > b.w; }
} e[MAXM << 1];  //无向图开两倍边

int head[MAXN];  // head[i]表示最后出现的以i为起点的边
bool vis[MAXN];  //访问过了的点
void add(int u, int v, int w) {
    e[cnt].from = head[u];  //记录上次出现的边
    e[cnt].to = v;
    e[cnt].w = w;
    head[u] = cnt++;  //更新本次出现的边,然后cnt++
}
priority_queue<node> q;
void init() {
    while (!q.empty()) q.pop();
    memset(head, 0, sizeof(head));
    memset(vis, 0, sizeof(vis));
    cnt = 1;
    ans = 0;
}
void prim() {
    int now = 1;      //从点1开始拓展
    int tot = n - 1;  //最多找n-1次
    while (tot--) {
        vis[now] = true;  //记录已经访问过的点
        //找以now点出发的所有边,将终点没有被访问的加入队列
        for (int i = head[now]; i != 0; i = e[i].from) {
            if (!vis[e[i].to]) {
                q.push(e[i]);
            }
        }
        node t = q.top();  //从当前的可拓展边中找出一条最短的
        q.pop();
        while (vis[t.to]) {  //弹出所有已经被访问过的边
            t = q.top();
            q.pop();
        }
        now = t.to;
        ans += t.w;
    }
}
int main(void) {
    scanf("%d%d", &n, &m);
    init();
    int u, v, w;
    for (int i = 0; i < m; i++) {
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w);
        add(v, u, w);
    }
    prim();
    printf("%d\n", ans);
    return 0;
}

最小生成树两连

标签:bre   起点   按秩合并   选中   code   适用于   开始   printf   数据结构   

原文地址:https://www.cnblogs.com/hermitgreen/p/12652586.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有 京ICP备13008772号-2
迷上了代码!