Prim 最小生成樹 圖解

self_disc發表於2022-04-29

什麼是生成樹

子圖:G=<V,E>,G'=<V', E'>,為兩個圖(V為點集,即圖中點的集合,E為邊集),如果V'是V的子集且E'是E的子集,則G'是G的子圖。

如果V'=V,則稱G'為G的生成子圖

如果G'是無向生成子圖且是樹的結構,則為生成樹

最小生成樹

最小生成樹:是一張有權無向連通圖中邊權和最小的生成樹

Prme演算法:

維護一個已經加入最小生成樹的點的集合C,每次通過一條邊連線一個不在這個點集C的點,直到最後形成一個樹形結構

Dist(u)表示u點到點集C中的點的最小距離

每次選擇一個到點集C距離最小的點加入點集C,並通過加入的點去更新未加入的點到點集C的最小距離(因為C中多加了一個點),直到n個點全部加入點集C或沒有點能夠加入(不能構成連通圖)。

圖解

Prim 最小生成樹 圖解

前言:已經加入點集C的點標記為藍色,當前加入的點標記為紅色,被當前加入的點更新的dist標記為紅色。

初始:加入一個初始點A,並通過A更新dist

u A B C D E F
dist(u) 0 3 5 inf inf inf

加入第二個點B:B到點集C距離最小,並通過B更新dist

u A B C D E F
dist(u) 0 3 1 8 3 inf

加入第三個點C:C到點集C距離最小,並通過C更新dist

u A B C D E F
dist(u) 0 3 1 8 3 inf

加入第四個點E:E到點集C距離最小,並通過E更新dist

u A B C D E F
dist(u) 0 3 1 2 3 1

 加入第五個點F:F到點集C距離最小,並通過F更新dist

u A B C D E F
dist(u) 0 3 1 2 3 1

 加入第六個點D:D到點集C距離最小,並通過D更新dist

u A B C D E F
dist(u) 0 3 1 2 3 1

 點全部加入點集,Prim演算法結束。

複雜度分析:

總共需要加入n個點,每次需要遍歷dist陣列找最小值,並通過該點更新未加入點集的dist值,即列舉該點連出的邊更新對應的dist,故複雜度為:

        O(n*n)+  \sum mi  = O(n*n + m)(mi為每個點連出的邊的條數,總和為總邊數)

虛擬碼:

 int prim()

{

    memset(dis, 127, sizeof(dis)); //初始設定為正無窮

    memset(vis, 0, sizeof(vis));   //初始設定點均不在點集中,點集為空

    ans = 0, cnt = 0;              //初始權值為0

    dis[1] = 0;                    // 1加入點集

    while (1)

    {

        int u = -1;

        for (int i = 1; i <= n; i++)

        {

            if (vis[i] == 0 && dis[i] < (1 << 30)) // i點不在點集中並且與點集中的點聯通

            {

                if (u == -1 || dis[i] < dis[u]) // u==-1 ->第一個點可以更新到點集最近的點

                {

                    u = i; //更新最近的點

                }

            }

        }

        if (u == -1)

            break;            //如果不能找到加入點集的點,則結束演算法

        cnt++, ans += dis[u]; //點集中點的個數+1,ans加上u連入點集的邊權

        vis[u] = true;        // vis加入點集

        for (auto it : a[u])//a[u]為以u連出的邊的點的集合,v為相連的點,w為邊權

        {

            dis[it.v] = min(dis[it.v], it.w); //通過點v連出的邊更新不在點集的點的dist值

        }

    }

    if (cnt == n)

        return ans; //能夠加入n個點構成連通圖,生成樹則返回權值

    else

        return -1; //不能形成生成樹

}

模板題 

題目連結:最小生成樹1 - 題目 - Daimayuan Online Judge

題目描述:

給你一張簡單無向連通圖,邊權都為非負整數。你需要求出它的最小生成樹,只需要輸出邊的權值和即可。

圖用以下形式給出:

第一行輸入兩個整數 n,m,表示圖的頂點數、邊數,頂點編號從 1 到 n。

接下來 m 行,每行三個整數 x,y,z 表示 x 與 y 之間有一條邊,邊權為 z。

輸入格式:

第一行兩個整數 n,m。

接下來 m 行,每行有三個整數,代表一條邊。

輸出格式:

輸出一個數,表示最小生成樹的權值和。

資料規模:

對於所有資料,保證 2≤n≤1000,n−1≤m≤100000,1≤x,y≤n,x≠y,1≤z≤10000

樣例輸入:

4 4

1 2 1

2 3 3

3 4 1

1 4 2

樣例輸出:

 詳見程式碼:

#include <bits/stdc++.h>
using namespace std;
int dis[100009], cnt, ans, n, m; // dis為點到點集的最小距離,cnt為點集中點的個數,ans為當前的邊權和
bool vis[100009];
struct node
{
    int v, w;
};
vector<node> a[100009]; //存圖
int prim()
{
    memset(dis, 127, sizeof(dis)); //初始設定為正無窮
    memset(vis, 0, sizeof(vis));   //初始設定點均不在點集中,點集為空
    ans = 0, cnt = 0;              //初始權值為0
    dis[1] = 0;                    // 1加入點集
    while (1)
    {
        int u = -1;
        for (int i = 1; i <= n; i++) //遍歷找未加入點集的最小距離的點
        {
            if (vis[i] == 0 && dis[i] < (1 << 30)) // i點不在點集中並且與點集中的點聯通
            {
                if (u == -1 || dis[i] < dis[u]) // u==-1 ->第一個點可以更新到點集最近的點
                {
                    u = i; //更新最近的點
                }
            }
        }
        if (u == -1)
            break;            //如果不能找到加入點集的點,則結束演算法
        cnt++, ans += dis[u]; //點集中點的個數+1,ans加上u連入點集的邊權
        vis[u] = true;        // vis加入點集
        for (auto it : a[u])
        {
            dis[it.v] = min(dis[it.v], it.w); //通過點v連出的邊更新不在點集的點的dist值
        }
    }
    if (cnt == n)
        return ans; //能夠加入n個點構成連通圖,生成樹則返回權值
    else
        return -1; //不能形成生成樹
}
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        node t1, t2; //無向圖存邊
        t1.v = v, t1.w = w;
        a[u].push_back(t1); // u->v 邊權為w
        t2.v = u, t2.w = w;
        a[v].push_back(t2); // v->u 邊權為w
    }
    cout << prim();
}
Prim 最小生成樹 圖解

 參考文獻:

2022 Namomo Spring Camp Div2 Day10 直播課

ending

有什麼錯誤之處歡迎指正!不勝感激!

相關文章