1. 程式人生 > >最小生成樹之Prim算法

最小生成樹之Prim算法

mark 分類 unsigned 最短 數學 沒有 下一個 數量 emp

普裏姆算法(Prim算法),圖論中的一種算法。可在加權連通圖裏搜索最小生成樹。

意即由此算法搜索到的邊子集所構成的樹中,不但包含了連通圖裏的全部頂點。且其全部邊的權值之和亦為最小。該算法於1930年由捷克數學家沃伊捷赫·亞爾尼克發現;並在1957年由美國計算機科學家羅伯特·普裏姆獨立發現。1959年,艾茲格·迪科斯徹再次發現了該算法。因此,在某些場合,普裏姆算法又被稱為DJP算法、亞爾尼克算法或普裏姆-亞爾尼克算法。我們之前介紹的Kruskal算法適用於稀疏圖(一般我們覺得滿足|E| < V*(V-1)/4時。圖為稀疏圖, |E|為邊的數量,V為頂點數)。

我們將要介紹的Prim算法則是適用於稠密圖(我們這裏所說的適用於某種情況。僅僅表示該算法在這個條件下效率最優)。

1、算法描寫敘述

從單一頂點開始,普裏姆算法依照下面步驟逐步擴大樹中所含頂點的數目,直到遍及連通圖的全部頂點。

輸入:一個加權連通圖,當中頂點集合為V,邊集合為E
輸出:使用集合Vnew和Enew來描寫敘述所得到的最小生成樹

算法流程:

  1.初始化:Vnew = {x},當中x為集合V中的任一節點(起始點),Enew = {}。

  2.反復下列操作,直到Vnew = V:
    <1>在集合E中選取權值最小的邊(u, v),當中u為集合Vnew中的元素。而v則是V中沒有增加Vnew的頂點(假設存在有多條滿足前述條件即具有同樣權值的邊。則可隨意選取當中之中的一個);
    <2>將v增加集合Vnew中,將(u, v)增加集合Enew中。

以下給出一個無向圖,每條邊上的數字為權值:

技術分享

我們任選一個頂點作為起始點。這裏我們隨便選一個。就以D作為起始點。

如今集合Vnew = { D }, Enew = { }。頂點A、B、E和F通過單條邊與D相連。A是距離D近期的頂點,因此將A及相應邊AD以高亮表示(下同)。由於頂點A是距離集合Vnew近期的點。所以我們將A增加集合。所以如今集合為 Vnew = { A, D}, Enew = { (A, D) }。

技術分享

下一個頂點為距離集合Vnew近期的頂點(也就是距離D或A近期的點)。B距D為9,距A為7。E為15,F為6。因此,F距D或A近期。所以我們將頂點F增加集合Vnew,將邊(D, F)增加集合 Enew。如今集合變為 Vnew = { A。 D, F }, Enew = { (A, D) , (D, F) }。

技術分享

我們繼續反復上面的步驟。

我們能夠發現距離集合Vnew近期的點為B,(A, B)距離為 7 。所一我們將B增加集合Vnew。 將邊(A, B)增加集合Enew。如今集合就變為 Vnew = { A, B, D, F }, Enew = { (A, B), (A, D), (D, F) }。

技術分享

我們僅僅要不斷的反復上述步驟,非常快我們就找到了該圖的最小生成樹(如圖所看到的)

技術分享

有興趣的朋友,還能夠試試用其它頂點作為起點看看答案是否一致。最後你會驚奇的發現不管你取哪一個點。最後的答案都是一致的。

2、Prim算法的時間復雜度

Prim算法循環|V| - 1,每次都要尋找距離集合Vnew的最小值。 掃描與一個點所連接的全部邊。

假設使用將一個點全部的邊都掃描一遍的算法,則時間復雜度為O(|V|2 + |E|)。

假設我們使用二叉堆來實現查找距離集合Vnew的最小值。則時間復雜度為O(|E| log |V| )。

假設使用斐波那契堆優化的話,那麽時間復雜度將能夠近一步優化為O(|E| + |V | log|V|)。

3*、Prim算法的證明(不感興趣的能夠直接跳過)

設Prim生成的樹為G0

如果存在Gmin使得cost(Gmin)<cost(G0)

則在Gmin中存在(u,v)不屬於G0

將(u,v)增加G0中可得一個環,且(u,v)是該環的最長邊

這與prim每次生成最短邊矛盾

故如果不成立,得證.

4、Prim算法的實現

這裏我們就用一到題目來說明Prim算法的實現 還是暢通project 。大家能夠先思考思考,看看能不能依據上面的描寫敘述自己實現Prim算法。以下附上這一題的代碼,以供參考:

【未優化版】

#include <cstdio>
#include <vector>
#define INF 0xfffffff
#define MAXN 100 + 10
using namespace std;
struct Vex{
    int v, weight;
    Vex(int tv, int tw):v(tv), weight(tw){}
};
vector<Vex> graph[MAXN];
bool inTree[MAXN];
int mindist[MAXN];

void Init(int n){
    for(int i = 1; i <= n; i++){
        mindist[i] = INF;
        inTree[i] = false;
        graph[i].clear();
    }
}

int Prim(int s, int n){
    int addNode, tempMin, tempVex ,ret = 0;
    //將頂點S增加集合Vnew
    inTree[s] = true;
    //初始化,各點到集合Vnew的距離, 數組mindist表示各點到集合Vnew的最小距離
    for(unsigned int i = 0; i < graph[s].size(); i++)
        mindist[graph[s][i].v] = graph[s][i].weight;
    //由於還有n-1個點沒有增加集合Vnew。所以還要進行n-1次操作
    for(int NodeCount = 1; NodeCount <= n-1; NodeCount++){
        tempMin = INF;
        //在還沒有增加集合Vnew的點中查找距離集合Vnew最小的點
        for(int i = 1; i <= n; i++){
            if(!inTree[i] && mindist[i] < tempMin){
                tempMin = mindist[i];
                addNode = i;
            }
        }
        //將距離集合Vnew距離最小的點增加集合Vnew
        inTree[addNode] = true;
        //將新增加邊的權值計入ret
        ret += tempMin;
        //更新還沒有增加集合Vnew的點 到 集合Vnew的距離
        for(unsigned int i = 0; i < graph[addNode].size(); i++){
            tempVex = graph[addNode][i].v;
            if(!inTree[tempVex] && graph[addNode][i].weight < mindist[tempVex]){
                mindist[tempVex] = graph[addNode][i].weight;
            }
        }
    }
    return ret;
}

int main(){
    int n;
    int v1, v2, weight;
    while(scanf("%d", &n), n){
        Init(n);
        for(int i = 0; i < n*(n-1)/2; i++){
            scanf("%d%d%d", &v1, &v2, &weight);
            graph[v1].push_back(Vex(v2, weight));
            graph[v2].push_back(Vex(v1, weight));
        }
        printf("%d\n", Prim(1, n));
    }
    return 0;
}

【堆優化版】

#include <cstdio>
#include <vector>
#include <queue>
#define INF 0xfffffff
#define MAXN 100 + 10
using namespace std;
struct Vex{
    int v, weight;
    Vex(int tv = 0, int tw = 0):v(tv), weight(tw){}
    bool operator < (const Vex& t) const{
        return this->weight > t.weight;
    }
};
vector<Vex> graph[MAXN];
bool inTree[MAXN];
int mindist[MAXN];

void Init(int n){
    for(int i = 1; i <= n; i++){
        mindist[i] = INF;
        inTree[i] = false;
        graph[i].clear();
    }
}

int Prim(int s, int n){
    priority_queue<Vex> Q;
    Vex temp;
    //res用來記錄最小生成樹的權值之和
    int res = 0;
    //將s增加集合Vnew。並更新與點s相連接的各點到集合Vnew的距離
    inTree[s] = true;
    for(unsigned int i = 0; i < graph[s].size(); i++){
        int v = graph[s][i].v;
        if(graph[s][i].weight < mindist[v]){
            mindist[v] = graph[s][i].weight;
            //更新之後。增加堆中
            Q.push(Vex(v, mindist[v]));
        }
    }
    while(!Q.empty()){
        //取出到集合Vnew距離最小的點
        temp = Q.top();
        Q.pop();
        int addNode = temp.v;
        if(inTree[addNode]) continue;
        inTree[addNode] = true;
        res += mindist[addNode];
        //更新到集合Vnew的距離
        for(unsigned int i = 0; i < graph[addNode].size(); i++){
            int tempVex = graph[addNode][i].v;
            if(!inTree[tempVex] && mindist[tempVex] > graph[addNode][i].weight){
                mindist[tempVex] = graph[addNode][i].weight;
                Q.push(Vex(tempVex, mindist[tempVex]));
            }
        }
    }
    return res;
}

int main(){
    int n;
    int v1, v2, weight;
    while(scanf("%d", &n), n){
        Init(n);
        for(int i = 0; i < n*(n-1)/2; i++){
            scanf("%d%d%d", &v1, &v2, &weight);
            graph[v1].push_back(Vex(v2, weight));
            graph[v2].push_back(Vex(v1, weight));
        }
        printf("%d\n", Prim(1, n));
    }
    return 0;
}

假設不了解priority_queue的朋友能夠參考:Here

【斐波那契堆優化】

先挖個坑,以後再填,有興趣的朋友能夠自行完好。

想找一些題練練手朋友,能夠移步到這裏:圖論題目分類



最小生成樹之Prim算法