最小生成樹 Prim 算法和Kruskal算法 分析加應用

概念 最小生成樹 MST
一顆無迴路的 有 v-1個邊 的樹
包含全部的結點的 生成樹
生成樹:任意加一條邊都一定會構成迴路

Prim 算法

讓一顆樹長大
1. 把一個初始點加入到 待拿取的數組中 (我是用的優先隊列 - 下面再說)
2. 開始循環 現在第一個初始點就是一個樹 我們要找離這個 樹權值最小的節點
3. 用數組的話 可以跑循環來找,而優先隊列 = 堆,我把他設置成一個最小堆,然後每次拿取第一個元素
4. 拿了之後就是把他加入到樹中成爲樹的一部分,現在就要更新別的的點了,我們有一個專門的數組dist(即distance 間距) – 他用來放每個點距離樹的權值,現在這個點已經是樹的一部分了,所以這個點的鄰接點距離樹的權值就需要改變
過程就是這麼個過程
下面看代碼 註釋很詳細 我是用的鄰接表
不懂優先隊列的可以去點下面的鏈接 用法很簡單

優先隊列

適用 特點
時間複雜度
鄰 接 表 : O(ElogN)
鄰接矩陣 :O(N*N)
與圖中的邊數無關
考點:因此適用於求邊稠密的網
稠密圖 不管鄰接矩陣還是鄰接表 都是N*N
Prim算法是依賴於點的算法
// Prim
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
using pii = pair<int, int>;
#define MAX 100
const int inf = 0x3f3f3f3f;
struct ENode
{
    int v1, v2;
    int weight;
};

vector<ENode> MST;
// 結點距離樹的權值
int dist[MAX];
int parent[MAX];
bool used[MAX];
int N;

// 圖 - 鄰接點
struct node
{
    // x是鄰接點
    int x;
    // 一個點到x的權值
    int weight;
};
vector<node> rode[MAX];
int quan[MAX];
int Prim()
{
    int totalWeight = 0, VCount = 0;
    // 默認爲極大值
    // 此點距離樹的權值
    fill(dist, dist + N, inf);
    // 每個點的父節點(最後加入到樹上的父節點)
    fill(parent, parent + N, -1);
    // 起點
    dist[0] = 0;

    // 放還沒加入樹中的結點放入優先隊列裏面 == 最小堆
    // 以他們離樹的距離爲排列條件 <權值 , 點>
    // 過程 : 每一個都把新加入的一個點的鄰接點們放進q
    // 並且更新他們對於樹的權值(即 他們和他們-最近的-已在樹中-的鄰接點的距離)
    // 每次直接拿出一個權值最小的點 作爲加入到樹中的預備結點
    priority_queue<pii, vector<pii>, greater<pii>> q;
    // priority_queue<pii> pp;
    q.push({0, 0});

    while (!q.empty())
    {
        pii x = q.top();
        q.pop();
        // 權值 和 點
        int d = x.first, v = x.second;
        // 已經在樹中了
        if (used[v])
            continue;

        // 加入到樹
        MST.push_back({parent[v], v, d});
        // 權值和
        totalWeight += dist[v];
        // 標記在樹中
        used[v] = true;
        // 更新V點的所有鄰接點 距離樹的權值
        for (auto e : rode[v])
        {
            /* 
             如果v點到達e點的值 比 dist中e點到達樹的值要小 
             就要更新dist
             因爲現在v點就是樹的一部分
             */
            if (e.weight < dist[e.x])
            {
                dist[e.x] = e.weight;
                // 因爲是把點x連接在他的鄰接點 v 上
                parent[e.x] = v;
                // 從他的鄰接點中找下一條路
                // (其實不一定下一個就是v的鄰接點了,
                // 有可能v這個點出列了之後,有了比v大一點,
                // 但比別的包括新加入的v鄰接點都小的點了)
                q.push({dist[e.x], e.x});
            }
        }
    }
    cout << "v1->v2  weight" << endl;
    for (auto e : MST)
    {
        if (e.v1 == -1)
            continue;
        cout << e.v1 << "->" << e.v2 << "  " << e.weight << endl;
    }

    return MST.size() == N ? totalWeight : -1;
}
int main()
{
    int M;
    cin >> N >> M;
    for (int i = 0; i < M; i++)
    {
        int x, y, d;
        cin >> x >> y >> d;
        rode[x].push_back({y, d});
        rode[y].push_back({x, d});
    }
    int ans = Prim();
    cout << ans << endl;
    return 0;
}

/*
9 15
0 1 2
0 5 7
0 6 3
1 2 4
1 6 6
2 3 2
2 7 2
3 7 8
3 4 1
4 8 2
4 5 6
5 8 5
8 6 1
8 7 4
7 6 3
v1->v2  weight
0->1    2
0->6    3
6->8    1
8->4    2
4->3    1
3->2    2
2->7    2
8->5    5
18
*/
 

樣例

輸入
9 15
0 1 2
0 5 7
0 6 3
1 2 4
1 6 6
2 3 2
2 7 2
3 7 8
3 4 1
4 8 2
4 5 6
5 8 5
8 6 1
8 7 4
7 6 3
輸出
v1->v2 weight
0->1 2
0->6 3
6->8 1
8->4 2
4->3 1
3->2 2
2->7 2
8->5 5
18

Kruskal算法 克魯斯卡爾

將森林合併成樹
以邊爲單位進行存儲 兩點+權重
1. 以權重進行排序(有的是寫一個最小堆 和直接排序時間一樣 排序好些啊)
2. 然後依次掃描邊集合
現在所有的邊都是按照權重進行排序的
3. 掃描的時候看看這兩個點是不是已經連通了 就用到了並查集
4. 沒連通連上即可 連通了就不用管了 一定是之前有更小的權重和別的的點連上了
我感覺Kruskal寫起來簡單 理解也不難
適用 分析
時間複雜度
O(ElongE) E是邊的數目
適用於邊稀疏的網
Kruskal算法是依賴邊的算法
在邊越少的情況下,Kruskal算法相對Prim算法的優勢就越大
#include <iostream>
#include <algorithm>
#include <vector>
#include <numeric>
using namespace std;
int N, M;
struct edge
{
    int x, y, weight;
};
vector<edge> edges;
vector<edge> MST;
vector<int> p;
int find(int x)
{
    return p[x] == x ? x : p[x] = find(p[x]);
}
int Kruskal()
{
    int SumWeight = 0, cnt = 0;
    sort(edges.begin(), edges.end(), [](auto &e1, auto &e2) { return e1.weight < e2.weight; });
    p.resize(N + 1);
    iota(p.begin(), p.end(), 0);
    for (auto &e : edges)
    {
        int x = e.x;
        int y = e.y;
        int d = e.weight;
        x = find(x);
        y = find(y);
        if (x == y)
            continue;
        SumWeight += d;
        p[x] = y;
        // y是x通向的邊
        MST.push_back({e.x, e.y, e.weight});

        if (++cnt == N - 1)
            break;
    }
    return cnt == N - 1 ? SumWeight : -1;
}
int main()
{
    cin >> N >> M;
    edges.resize(N);
    for (int i = 0; i < M; i++)
    {
        int x, y, d;
        cin >> x >> y >> d;
        edges.push_back({x, y, d});
    }
    cout << Kruskal();
    cout << endl;
    for (auto e : MST)
    {
        cout << e.x << "->" << e.y << "  " << e.weight << "  \n";
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章