[洛谷] P3366 【模板】最小生成樹 --- Kruskal

題目描述

如題,給出一個無向圖,求出最小生成樹,如果該圖不連通,則輸出orz

輸入輸出格式

輸入格式:
第一行包含兩個整數(shù)N、M,表示該圖共有N個結(jié)點(diǎn)和M條無向邊。(N<=5000,M<=200000)

接下來M行每行包含三個整數(shù)Xi、Yi、Zi,表示有一條長度為Zi的無向邊連接結(jié)點(diǎn)Xi、Yi

輸出格式:
輸出包含一個數(shù),即最小生成樹的各邊的長度之和;如果該圖不連通則輸出orz

輸入輸出樣例

輸入樣例#1:
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
輸出樣例#1:
7
1.Kruskal算法簡介
Kruskal算法一般稱作克魯斯卡爾算法。克魯斯卡爾算法是一種用來尋找最小生成樹的算法。在剩下的所有未選取的邊中,找最小邊,如果和已選取的邊構(gòu)成回路,則放棄,選取次小邊。
2.Kruskal算法思想
先構(gòu)造一個只含 n 個頂點(diǎn)、而邊集為空的子圖,把子圖中各個頂點(diǎn)看成各棵樹上的根結(jié)點(diǎn),之后,從網(wǎng)的邊集 E 中選取一條權(quán)值最小的邊,若該條邊的兩個頂點(diǎn)分屬不同的樹,則將其加入子圖,即把兩棵樹合成一棵樹,反之,若該條邊的兩個頂點(diǎn)已落在同一棵樹上,則不可取,而應(yīng)該取下一條權(quán)值最小的邊再試之。依次類推,直到森林中只有一棵樹,也即子圖中含有 n-1 條邊為止。
  時(shí)間復(fù)雜度為為O(e^2), 使用并查集優(yōu)化后復(fù)雜度為 O(eloge),與網(wǎng)中的邊數(shù)有關(guān),適用于求邊稀疏的網(wǎng)的最小生成樹。
3.Kruskal算法解題過程
首先我們需要解決一個問題,不能構(gòu)成回路,這時(shí),我們可以先使用并查集的思想,先把每條邊存為獨(dú)立的根,然后,尋找最短邊權(quán)時(shí),我們可將已經(jīng)找過的邊合并到集內(nèi),防止構(gòu)成回路,選擇其他的次小邊。

2259.png

接著就是Kruskal處理核心,我們需要先將邊進(jìn)行排序,這樣有利于后面選擇最小邊。
循環(huán)0-n次 且 i < n && nEdge != m - 1,為什么要nEdge != m - 1呢?
這個意思就表達(dá)了這個圖是不連通的,也同樣等價(jià)于不存在最小生成樹。
接著,我們可以查找這兩邊的根,如果他們的根節(jié)點(diǎn)都不一樣,就表示能夠增加這個邊權(quán),它既是最小的,并且也不會構(gòu)成回路。然后聲明一個記住最小邊權(quán)變量,最后輸出既完成本題目。
下面是具體的代碼實(shí)現(xiàn)

#include <cstdio>
#include <cstdlib>
#define MAXN 200000 + 10//對于100%的數(shù)據(jù)
using namespace std;

int par[MAXN], Rank[MAXN];//并查集 rank
typedef struct {
    int a, b, price;
}Node;//結(jié)構(gòu)體儲存
Node a[MAXN];

int cmp(const void*a, const void *b) {
    return ((Node*)a)->price - ((Node*)b)->price;//用于比較邊權(quán)
}
void Init(int n) {
    for (int i = 0; i < n; i++) {
        Rank[i] = 0;
        par[i] = i;
    }
}

int find(int x) {
    int root = x;
    while (root != par[root]) root = par[root];
    while (x != root) {
        int t = par[x];
        par[x] = root;
        x = t;
    }
    return root;
}

void unite(int x, int y) {
    x = find(x);
    y = find(y);
    if (Rank[x] < Rank[y]) {
        par[x] = y;
    }
    else {
        par[y] = x;
        if (Rank[x] == Rank[y]) Rank[x]++;
    }
}
//n為邊的數(shù)量,m為村莊的數(shù)量
int Kruskal(int n, int m) {
    int nEdge = 0, res = 0;
    //將邊按照權(quán)值從小到大排序
    qsort(a, n, sizeof(a[0]), cmp);
    for (int i = 0; i < n && nEdge != m - 1; i++) {
        //判斷當(dāng)前這條邊的兩個端點(diǎn)是否屬于同一棵樹
        if (find(a[i].a) != find(a[i].b)) {
            unite(a[i].a, a[i].b);
            res += a[i].price;
            nEdge++;
        }
    }
    //如果加入邊的數(shù)量小于m - 1,則表明該無向圖不連通,等價(jià)于不存在最小生成樹
    if (nEdge < m - 1) res = -1;
    return res;
}
int main() {
    int n, m, ans;
    scanf("%d%d", &n, &m);
    Init(n);
    for (int i = 0; i < m; i++) {
        scanf("%d%d%d", &a[i].a, &a[i].b, &a[i].price);
        //將村莊編號變?yōu)?~m-1(這個僅僅只是個人習(xí)慣,并非必要的)
        a[i].a--;
        a[i].b--;
    }
    int rets = Kruskal(m, n);//計(jì)算最小生成樹
    //輸出結(jié)果
    if (rets == -1)
        printf("orz");
    else printf("%d", rets);
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 第一章 緒論 什么是數(shù)據(jù)結(jié)構(gòu)? 數(shù)據(jù)結(jié)構(gòu)的定義:數(shù)據(jù)結(jié)構(gòu)是相互之間存在一種或多種特定關(guān)系的數(shù)據(jù)元素的集合。 第二章...
    SeanCheney閱讀 5,807評論 0 19
  • 1 序 2016年6月25日夜,帝都,天下著大雨,拖著行李箱和同學(xué)在校門口照了最后一張合照,搬離寢室打車去了提前租...
    RichardJieChen閱讀 5,148評論 0 12
  • 整理自《數(shù)據(jù)結(jié)構(gòu)高分筆記》 1、普里姆算法 算法思想普利姆算法的基本思想如下:從圖中任意取出一個頂點(diǎn),把它當(dāng)成一棵...
    文哥的學(xué)習(xí)日記閱讀 6,747評論 0 2
  • 草莓、圣女果 應(yīng)邀著干紅的孤獨(dú) 就著鋼琴曲《罪孽》的優(yōu)美旋律 在這月明星稀的黑夜中 肆無忌憚地邂逅著、詮釋著...
    格桑之戀閱讀 320評論 5 6
  • 吃早點(diǎn),把車停在了門口小徑的一側(cè)。邊走邊看到一輛紅色電動三輪車停在了早點(diǎn)店的門口,正門口,從車上下來了一位紅衣大媽...
    金金心閱讀 132評論 0 0