1. 程式人生 > >最小生成樹之普里姆(prim)演算法(C++實現)

最小生成樹之普里姆(prim)演算法(C++實現)

最小生成樹之普里姆(Prim)演算法

 

最小生成樹:是在一個給定的無向圖G(V,E)中求一棵樹T,使得這棵樹擁有圖G中的所有頂點,且所有邊都是來自圖G中的邊,並且滿足整棵樹的邊權之和最小。

如上圖給出了一個圖G及其最小生成樹T,其中紅色的線即為最小生成樹的邊。最小生成樹T包含了圖G中所有的頂點,且由它們生成的樹的邊權之和為15,是所有生成樹中權值最小的。
最小生成樹有3個性質:
(1)最小生成樹是樹,因此其邊數等於定點數減1,且樹內一定不會有環;
(2)對給定的圖G(V,E),其最小生成樹可以不唯一,但是其邊權之和一定是唯一的;
(3)由於最小生成樹是無向圖上生成的,因此其根結點可以是這棵樹上的任意一個結點。

Prim演算法
Prim演算法是用來解決最小生成樹問題的。
基本思想:對圖G(V,E)設定集合S,存放已經被訪問的頂點,然後每次從集合V-S中選擇與集合S的最短距離最小的一個頂點(記為u),訪問並加入集合S。之後,令頂點u為中介點,優化所有從u能到達的頂點v與集合S之間的最短距離。這樣的操作執行n次(n為頂點個數),直到集合S已包含所有頂點。
注意:可以發現,prim演算法的思想和最短路徑中Dijkstra演算法的思想幾乎完全相同,只是在涉及最短距離時使用了集合S代替Dijkstra演算法中的起點s。
下面舉例來說明一下prim是如何求最小生成樹的。
假設從頂點V0開始,當前集合V={V0,V1,V2,V3,V4,V5}(藍色),集合S={}(黃色),頂點V0與集合S之間的距離為0,其它均為INF(一個很大的數):
(1)如圖(a),選擇與集合S距離最小的頂點V0,將其加入到集合S中,並連線頂點V0與其它頂點的邊,此時集合V={V1,V2,V3,V4,V5},集合S={V0};
(2)如圖(b),選擇與集合S距離最小的頂點V4,將其加入到集合S中,並連線頂點V4與其它頂點的邊,此時集合V={V1,V2,V3,V5},集合S={V0,V4};
(3)如圖(c),選擇與集合S距離最小的頂點V5,將其加入到集合S中,並連線頂點V5與其它頂點的邊,此時集合V={V1,V2,V3},集合S={V0,V4,V5};
(4)如圖(d),選擇與集合S距離最小的頂點V1,將其加入到集合S中,並連線頂點V1與其它頂點的邊,此時集合V={V2,V3},集合S={V0,V1,V4.V5};
(5)如圖(e),選擇與集合S距離最小的頂點V3,將其加入到集合S中,並連線頂點V3與其它頂點的邊,此時集合V={V2},集合S={V0,V1,V3,V4,V5};
(6)如圖(f),最後選擇頂點V2,將其加入到集合S中,此時集合V={},集合S={V0,V1,V2,V3,V4,V5};
此時集合S已經包含了所有的頂點,演算法結束。

Prim演算法流程:
對圖G(V,E)設定集合S,存放已經被訪問的頂點,然後執行n次下面的兩個步驟(n為頂點個數):
每次從集合V-S中選擇與集合S的最短距離最小的一個頂點(記為u),訪問並加入集合S,同時把這條離集合最近的邊加入到最小生成樹中。
令頂點u為中介點,優化所有從u能到達的未訪問的頂點v與集合S之間的最短距離。
具體實現:
prim演算法需要實現兩個關鍵的概念,即集合S的實現,頂點Vi(0<=i<=n-1)與集合S的最短距離。
集合S的實現使用一個bool型陣列vis[]表示頂點是否已經被訪問,其中vis[i]=true表示頂點Vi已被訪問,vis[i]=false則表示頂點Vi未被訪問;
令int型陣列d[]來存放頂點Vi與集合S的最短距離。初始時除了起點s的d[s]=0,其餘頂點賦值為一個很大的數來表示INF,即不可達。
 

程式碼:

main.cpp

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

const int INF = 1e9;

struct Node
{
    int v;
    int dis;
    Node(int x, int y): v(x), dis(y) {}
};

int PrimG(int n, int s, vector<vector<int>> G, vector<bool> &vis, vector<int> &d)
{
    /*
     * n:         頂點個數
     * s:         初始點
     * G:       圖的鄰接矩陣
     * vis:       標記頂點是否被訪問
     * d:         儲存起點s到其它頂點的最短距離
     * return:    最小生成樹的邊權之和
     */
    fill(vis.begin(), vis.end(), false);
    fill(d.begin(), d.end(), INF);
    d[s] = 0;
    int sum = 0;
    for (int i = 0; i < n; ++i)
    {
        int u = -1;
        int MIN = INF;
        for (int j = 0; j < n; ++j)
        {
            if (!vis[j] && d[j] < MIN)
            {
                MIN = d[j];
                u = j;
            }
        }

        // 找不到小於INF的d[u],則剩下的頂點與集合S不連通
        if (u == -1)
        {
            return -1;
        }
        vis[u] = true;
        sum += d[u];
        for (int v = 0; v < n; ++v)
        {
            if (!vis[v] && G[u][v] != INF && G[u][v] < d[v])
            {
                // 若將d[v]改為struct d {u, v, cost}, 則可以記錄MST的每一條邊
                d[v] = G[u][v];
            }
        }
    }
    return sum;
}



int PrimAdj(int n, int s, vector<vector<Node>> Adj, vector<bool> &vis, vector<int> &d)
{
    /*
     * n:         頂點個數
     * s:         初始點
     * Adj:       圖的鄰接表
     * vis:       標記頂點是否被訪問
     * d:         儲存起點s到其它頂點的最短距離
     * return:    最小生成樹的邊權之和
     */
    fill(vis.begin(), vis.end(), false);
    fill(d.begin(), d.end(), INF);
    d[s] = 0;
    int sum = 0;
    for (int i = 0; i < n; ++i)
    {
        int u = -1;
        int MIN = INF;
        for (int j = 0; j < n; ++j)
        {
            if (!vis[j] && d[j] < MIN)
            {
                MIN = d[j];
                u = j;
            }
        }

        // 找不到小於INF的d[u],則剩下的頂點與集合S不連通
        if (u == -1)
        {
            return -1;
        }

        vis[u] = true;
        sum += d[u];
        for (size_t j = 0; j < Adj[u].size(); ++j)
        {
            int v = Adj[u][j].v;
            if (!vis[v] && Adj[u][j].dis < d[v])
            {
                // 若將d[v]改為struct d {u, v, cost}, 則可以記錄MST的每一條邊
                d[v] = Adj[u][j].dis;
            }
        }
    }
    return sum;
}

int main()
{
    int n = 6;
    /*鄰接矩陣*/
    vector<vector<int>> G = {{  0,  4,INF,INF,  1,  2},
                             {  4,  0,  6,INF,INF,  3},
                             {INF,  6,  0,  6,INF,  5},
                             {INF,INF,  6,  0,  4,  5},
                             {  1,INF,INF,  4,  0,  3},
                             {  2,  3,  5,  5,  3,  0}};

    for (size_t i = 0; i < G.size(); ++i)
    {
        for (size_t j = 0; j < G[i].size(); ++j)
        {
            if (G[i][j] != INF && G[i][j] != 0)
            {
                cout << i << " " << j << " " << G[i][j] << endl;
            }
        }
    }
    cout << "--------" << endl;


    /*鄰接表*/
    vector<vector<Node>> Adj = {{Node(4,1),Node(5,2),Node(1,4)},
                                {Node(0,4),Node(5,3),Node(2,6)},
                                {Node(1,6),Node(3,6),Node(5,5)},
                                {Node(2,6),Node(4,4),Node(5,5)},
                                {Node(0,1),Node(5,3),Node(3,4)},
                                {Node(0,2),Node(1,3),Node(2,5),Node(3,5),Node(4,3)}};


    for (size_t i = 0; i < Adj.size(); ++i)
    {
        for (size_t j = 0; j < Adj[i].size(); ++j)
        {
            cout << i << " " << Adj[i][j].v << " " << Adj[i][j].dis << endl;
        }
    }
    cout << "--------" << endl;


    vector<bool> vis(n);
    vector<int> d(n);


    int resG = PrimG(n, 0, G, vis, d);    // 鄰接矩陣版
    int resAdj = PrimAdj(n, 0, Adj, vis, d);    // 鄰接表版

    cout << resG << endl << resAdj << endl;

    return 0;
}

注:上述程式碼只可以返回MST的cost值,若需要記錄MST的每一條邊,將d[v]修改為struct d {u, v, cost}即可。

 

執行結果:

參考部落格:https://blog.csdn.net/yf_li123/article/details/75148998