1. 程式人生 > 其它 >MST(最小生成樹)

MST(最小生成樹)

一、什麼是圖的最小生成樹(MST)

  • N個點用N-1條邊連線成一個連通塊,形成的圖形只可能是樹,沒有別的可能。

  • 一個有N個點的圖,邊一定是大於等於N-1條的。圖的最小生成樹,就是在這些邊中選擇N-1條出來,連線所有的N個點。這N-1條邊的邊權之和是所有方案中最小的。

二、最小生成樹用來解決什麼問題?

就是用來解決如何用最小的“代價”用N-1條邊連線N個點的問題。

三、Prim演算法

  • Prim演算法採用與Dijkstra、Bellman-Ford演算法一樣的“藍白點”思想:白點代表已經進入最小生成樹的點,藍點代表未進入最小生成樹的點。


演算法描述:(時間複雜度n的2次方(鄰接矩陣),鄰接表O(E log(n))其中E為邊數)
以1為起點生成最小生成樹,min[v]表示藍點v與白點相連的最小邊權,MST表示最小生成樹的權值之和


虛擬碼

a)初始化:min[v]= ∞(v≠1); min[1]=0;MST=0;
b)for (i = 1; i<= n; i++)
    1.尋找min[u]最小的藍點u。
    2.將u標記為白點
    3.MST+=min[u]
    4.for 與白點u相連的所有藍點v  
      if (w[u][v]<min[v]) 
          min[v]=w[u][v];
c)演算法結束: MST即為最小生成樹的權值之和

堆優化code

int Prim(int st){
	priority_queue<node>q;
	for(int i=1;i<=n;++i)d[i]=0x3f3f3f3f;
	q.push((node){st,0});
	d[st]=0;
	int ans=0;
	while(!q.empty()){
		node x=q.top();
		q.pop();int id=x.id;
		if(!vis[id]){
			vis[id]=1;
			ans+=d[id];
			for(int i=head[id];i;i=e[i].next){
				int v=e[i].to;
				d[v]=min(d[v],e[i].dis);
				if(!vis[v]){
					q.push((node){v,d[v]});
				}
			}
		}
	}
	return ans;
}
  • 注意,這裡與dijistra的區別。dijistra中min[v]表示v與起點的距離,這裡表示與最近的藍點的距離

簡單證明

反證法

假設權值最小的邊不在最小生成樹中。
此時將權值最小的邊加入生成樹中,那麼必然會構成一 個迴路(最小生成樹有n個點,最小邊加入之後會有n條邊),任意去掉環裡比權值最小的邊權值大的一條邊,這樣就構造了更優的一個解,這時與假設構成矛盾。所以權值最小的邊一定在最小生成樹中。

四、Kruskal

- Kruskal演算法每次從剩下的邊中選擇一條不會產生環路的具有最小耗費的邊加入已選擇的邊的集合中。注意到所選取的邊若產生環路則不可能形成一棵生成樹。

Kruskal演算法分e 步,其中e 是網路中邊的數目。按耗費遞增的順序來考慮這e 條邊,每次考慮一條邊。當考慮某條邊時,若將其加入到已選邊的集合中會出現環路,則將其拋棄,否則,將它選入。
用並查集維護在同一棵樹上的點,如果邊上的兩點已經在一棵樹上,加邊後一定會有環。

Kruskal演算法在連邊的時候實際上是把兩個連通塊相連,如果圖本身連通則執行完之後應該只有一個連通塊

int liantongkuai=n;//一個點就是一個連通塊
int Kruscal(){
	for(int i=1;i<=n;++i)f[i]=i;
	sort(e+1,e+k+1,cmp);
	int m=0,ans=0;
	for(int i=1;i<=k;++i){
		if(m==n-1)break;
		int x=e[i].from,y=e[i].to;
		if(find(x)!=find(y)){
			ans+=e[i].dis;m++;Union(x,y);liantongkuai--;
		}
	}
	return ans;
}