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


剛學完最小生成樹,趕緊寫寫學習的心得(其實是怕我自己忘了)

最小生成樹概念:一個有 n 個結點的連通圖的生成樹是原圖的極小連通子圖,且包含原圖中的所有 n 個結點,並且有保持圖連通的最少的邊。

就是說如果我們想把一張有n個點的圖連接起來,那我們就只需要n-1條邊(原因顯然:就如同一條有n個點的線段,他們之間最少需要n-1條邊連起來)

最小生成樹就是尋找值最小的這n-1個點,把他們加和。

首先,最小生成樹最基本的算法是Prim和Kruskal算法

Prim算法

算法分析&思想講解:
Prim算法采用“藍白點”思想:白點代表已經進入最小生成樹的點,藍點代表未進入最小生成樹的點。
Prim算法每次循環都將一個藍點u變為白點,並且此藍點u與白點相連的最小邊權min[u]還是當前所有藍點中最小的。

這樣相當於向生成樹中添加了n-1次最小的邊,最后得到的一定是最小生成樹。

                                                                

    

Prim算法的好處就在於它與邊無關,主要用於稠密圖,復雜度為O(n^2),實用度不如Kruskal算法高

代碼介紹:(好像不可以直接用,有點問題)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int MAXN=5010;
int t[MAXN][MAXN];
bool b[MAXN];
int MIN[MAXN];
int main(){
     memset(b,false,sizeof(b));
     memset(t,127,sizeof(t));
     memset(MIN,127,sizeof(MIN));                //把每一條未賦值的邊賦為較大的一個數
    int n,m; 
    int ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)t[i][i]=0;
    for(int i=1;i<=n;i++){               //鄰接矩陣存圖 
       for (int j=1;j<=n;j++){           //不同問題存圖方式不同 
              cin>>t[i][j];
       }
    }
    MIN[1]=0;
 //先找點: 
    for(int i=1;i<=n;i++){
        int x=0;                        //x為0 就是說一開始是從一個虛擬點開始的 然后我們找與它相鄰的邊並且還沒被找過的點 
         for(int j=1;j<=n;j++){         
            if(!b[j]&&MIN[j]<MIN[x]){  //我們以這一個點開始尋找與它相鄰的最小的邊 
                x=j;                   //然后就標記這個點以便於接着用這個點繼續往下找 
            }
        }
    
         b[x]=true;                 //找完這個點后就變成白點,表示已找過 
    //再擴邊: 
         for(int j=1;j<=n;j++){          
             if(!b[j]&&MIN[j]>t[x][j]){   //這段代碼就是給我們剛找到的X點的鄰邊賦實際值,這樣在下次尋找X的最小邊時就可以找到啦 
                 MIN[j]=t[x][j];           //所以說找點的代碼就比較好理解了 
            }
        }
    }
   for(int i=1;i<=n;i++){
       ans+=MIN[i];//求最小和 
   }
    cout<<ans<<endl; 
    return 0;
}

知識擴展:本算法在移動通信、智能交通、移動物流、生產調度等物聯網相關領域都有十分現實的意義,采用好的算法,就能節省成本提高效率。

 

 

Kruskal算法:

算法分析:

Kruskal算法是將一個連通塊當做一個集合。Kruskal首先將所有的邊按從小到大順序排序(一般使用快排),並認為每一個點都是孤立的,分屬於n個獨立的集合。

然后按順序枚舉每一條邊。如果這條邊連接着兩個不同的集合,那么就把這條邊加入最小生成樹,這兩個不同的集合就合並成了一個集合(這就是一條邊);

如果這條邊連接的兩個點屬於同一集合(說明這條邊找過了),就跳過。直到選取了n-1條邊為止。

思路講解:

Kruskal算法每次都選擇一條最小的,且能合並兩個不同集合的邊,一張n個點的圖總共選取n-1次邊。因為每次我們選的都是最小的邊,所以最后的生成樹一定是最小生成樹。每次我們選的邊都能夠合並兩個集合,最后n個點一定會合並成一個集合。通過這樣的貪心策略,Kruskal算法就能得到一棵有n-1條邊,連接着n個點的最小生成樹。
  
Kruskal算法的時間復雜度為O(E*logE),E為邊數。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=10010;
int fa[MAXN];

int m,k,ans,x;
struct Edge{
    int s,t,w;
}edge[MAXN<<1];
int find(int x){
    if(fa[x]==x)return x;
    return fa[x]=find(fa[x]);
}
void unionn(int x,int y){
    int xx=find(x);
    int yy=find(y);
    if(xx!=yy){
        fa[xx]=yy;
    }
} 
int cmp(const Edge &a,const Edge &b){
    if(a.w<b.w)return 1;
    else return 0;
}
int main(){
    int n;
    cin>>n; 
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
        cin>>x;
        if(x!=0){
            m++;
            edge[m].s=i;
            edge[m].t=j;
            edge[m].w=x;
            }    
        }
    }
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(edge+1,edge+1+m,cmp);//按照權值大小排序 
    for(int i=1;i<=m;i++){
        if(find(edge[i].s)!=find(edge[i].t)){//查詢兩條邊是否在一個集合里 
            unionn(edge[i].s,edge[i].t);//因為是按最小值排序,我們所能選擇的肯定是最小的 
            ans+=edge[i].w;//然后加和 
            k++;//計邊的數 
        }
        if(k==n-1)break;//如果搜夠了n-1條邊就停止 
    }
    cout<<ans<<endl;
return 0;    
} 

 

 End...


免責聲明!

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



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