最小生成樹(Prim算法和Kruskal算法)


1)最小生成樹

給定一個無向圖,如果它的某個子圖中任意兩個頂點都互相連通並且是一棵樹,那么這棵樹就叫生成樹。如果邊上有權值,那么使得邊權和最小的生成樹叫做最小生成樹(MST,Minimum Spanning Tree)

2)應用

比如讓你為一個鎮的九個村庄架設通信網絡,每個村庄相當於一個頂點,權值是村與村之間可通達的直線距離,要求你必須用最小的成本完成這次任務;或者村庄之間建公路,連通N個村庄要N-1條路,如何讓建路成本最低之類的問題。

1、Prim算法

①該算法是構建最小生成樹的算法之一。它是以某頂點為起點,不斷添加各頂點上最小權值的邊,來構建最小生成樹。

②算法流程:

第一步:設圖G頂點集合為U,首先任意選擇圖G中的一點作為起始點a,將該點加入集合V;

第二步:從集合U中找到另一點b使得點b到V中任意一點的權值最小,此時將b點也加入集合V;

以此類推,現在的集合V={a,b},再從集合U中找到另一點c使得點c到V中任意一點的權值最小,此時將c點加入集合V;直至所有頂點全部被加入V,此時就構建出了一棵MST。

③參考代碼:

#include <iostream>
#include <string.h>
#define INT_MAX 1000000000
using namespace std;

int matrix[100][100];//鄰接矩陣
bool visited[100];//標記數組
int path[100];//記錄生成樹路徑
int lowcost[100];//邊的權值
int vertex_num,arc_num;//頂點數,弧數
int sum;//權值總和
int source;//源點
void prim(int source);
int main()
{
    cout << "請輸入圖的頂點數(<=100):";
    cin >> vertex_num;
    cout << "請輸入圖的弧數:";
    cin >> arc_num;

    for (int i = 0; i < vertex_num; i++)
        for (int j = 0; j < vertex_num; j++)
            matrix[i][j] = INT_MAX;  //初始化matrix數組

    cout << "請輸入弧的信息:\n";
    int u, v, w;
    for (int i = 0; i < arc_num; i++)
    {
        cin >> u >> v >> w;
        matrix[u][v] = matrix[v][u] = w;
    }

    cout << "請輸入起點(<" << vertex_num << "):";
    cin >> source;
    prim(source);

    cout << "最小生成樹權和為:" << sum << endl;
    cout << "最小生成樹路徑為:\n";
    for (int i = 0; i < vertex_num; i++)
        if (i != source)
            cout << i << "----" << path[i] << endl;

    return 0;
}
void prim(int source){
    memset(visited,0,sizeof(visited));
    visited[source]=true;
    for(int i=0;i<vertex_num;i++){
        lowcost[i] = matrix[source][i];
        path[i]=source;
    }

    int min_cost,min_cost_index;//最小的權值,和其下標
    sum=0;
    for(int i=1;i<vertex_num;i++){//找除源點外的n-1個點,如果這里寫多了,
                                //那么下邊的for里if進不去,那么sum的值也會錯誤
            min_cost=INT_MAX;

        for(int j=0;j<vertex_num;j++){//遍歷所有頂點
            if(visited[j]==false&&lowcost[j]<min_cost){//找到與源點的權值最小的點
                min_cost=lowcost[j];
                min_cost_index=j;
            }
        }
        visited[min_cost_index]=true;//講找到的頂點進行標記
        sum+=min_cost;//權值總和

        for(int j=0;j<vertex_num;j++){
            if(visited[j]==false&&matrix[min_cost_index][j]<lowcost[j]){//更新lowcost,以便找下個頂點
                lowcost[j]=matrix[min_cost_index][j];
                path[j] = min_cost_index;
            }
        }
    }


}

摘自http://www.61mon.com/index.php/archives/199/comment-page-1#comments

2、Kruskal算法

①該算法是構建最小生成樹的另一個算法,其是對邊進行操作,來構建最小生成樹的。

②算法流程:

第一步:把所有的邊按權值從小到大排列。

第二步:按照順序選取每條邊,如果這條邊的兩個端點不屬於同一集合,那么就將它們合並。

重復第二步,直到 所有的點都屬於同一個集合。     

第二步用並查集來實現,又因為每次都選的權值最小的,所以這其實就是運用並查集的貪心算法。

③為什么這樣做就可以構建最小生成樹呢?因為N個頂點只需要N-1條邊就夠了,那么選哪N-1條呢,為了讓權值和最小,當然選從小到大排序的前N-1條邊嘍。

④參考代碼:

 

#include <iostream>
#include <algorithm>
#define MAX_INT 100
using namespace std;
struct Edge{//
int a;//邊上的兩個點
int b;
int weight;//權值
}edge[MAX_INT];
int par[MAX_INT];//i的父親的編號
int rank[MAX_INT];//i的高度
int vertex_num,arc_num;//頂點數,弧數
int case_num;//測試組數
int sum;//權值總和

void init(int n);//初始化
int find(int x);//查找根節點並且壓縮路徑
bool  unite(int x,int y);//合並
bool compare(const Edge&a,const Edge&b);

int main()
{
    cout<<"請輸入測試組數:"<<endl;
    cin>>case_num;
    while(case_num){
        sum=0;
        cout<<"請輸入頂點數和弧數:"<<endl;
        cin>>vertex_num>>arc_num;

        init(vertex_num);

        cout<<"請輸入"<<arc_num<<"條弧的信息:"<<endl;
        for(int i=0;i<arc_num;i++){
            cin>>edge[i].a>>edge[i].b>>edge[i].weight;
            }

        sort(edge,edge+arc_num,compare);

        cout<<"最小生成樹的路徑為:"<<endl;
        int j;
        for(j=0;j<arc_num;j++){
            if(unite(edge[j].a,edge[j].b)){
                sum+=edge[j].weight;
                cout<<edge[j].a<<"----"<<edge[j].b<<" "<<edge[j].weight<<endl;
        }
        }
        if(j==arc_num){
            cout<<"最小生成樹的權值和為:"<<sum<<endl;
        }else if(j<arc_num){
            cout<<"data error!"<<endl;
        }

        case_num--;
    }
    return 0;
}
void init(int n){
    for(int i=0;i<n;i++){
        par[i]=i;//父節點都先初始化為自己
        rank[i]=0;//高度初始化為0
    }

}

int find(int x){
if(x==par[x]){
    return x;
}else{
    return par[x]=find(par[x]);
}
}
bool  unite(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y){
        return false;
    }else{
    if(rank[x]<rank[y]){
        par[x]=y;
    }else{
        par[y]=x;
        if(rank[x]==rank[y])
            rank[x]++;
    }
    return true;
    }

}

bool compare(const Edge&a,const Edge&b){//按從小到大的順序排序
    return a.weight<b.weight;
}

 

 

 

代碼參考自:http://blog.csdn.net/niushuai666/article/details/6689285

3、Kruskal算法和Prim算法的區別(或者說什么時候用Prim什么時候用Kruskal)?

Prim算法時間復雜度為,與網中的邊數無關,適用於求邊稠密的網的最小生成樹。

Kruskal算法時間復雜度為,適用於求稀疏的網的最小生成樹。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM