剛學完最小生成樹,趕緊寫寫學習的心得(其實是怕我自己忘了)
最小生成樹概念:一個有 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...