1. 程式人生 > 實用技巧 >圖論(2)--論最小生成樹

圖論(2)--論最小生成樹

圖論(2)--論最小生成樹

樹狀陣列??樹...樹.樹呢?? --\(zyx\)

上回書說到,簡單的最小路問題,那麼這次我們來談談最小生成樹問題.

最小生成樹是指在固定的\(n\)個點,\(m\)條邊的圖中,找出\(n-1\)條邊,使組成的圖的邊權最小且每個點之間可以相互到達(不一定是直接到達可以通過點的轉移).

我們知道由\(n\)個點,\(n-1\)條邊組成的圖不是一顆樹嗎??所以這就是我們所要探討的話題--最小生成樹的名字的來歷.

其實問題並不難,簡單的來說,就是找\(n-1\)條邊,使構成的圖裡沒有環,且邊權和最小.對於這個問題 我們提出兩種方法也是最為普遍的兩種方法

Part 1 Prim演算法

利用一種很NB的思想--紅黑樹,

思路

設圖的頂點集合為U,樹的頂點集合為V

從圖中任意一點出發,找到N-1條邊(x,y),x∈U,y∈V,且權值最小。

通俗的講,就是不斷找權值最小不產生閉環N-1條邊

舉例子

話不多說,先上圖

(1)\(V3\)出發

(2)找到邊(\(V3,V1\)),符合條件且最小,將\(V1\)加入\(V\)

以此類推……

(N)找到邊\((V2,V5)\),符合條件且最小,將\(V5\)加入\(V\),最小生成樹構造完成

/*Prim核心程式碼*/
for (int i=2;i<=n;i++)
    {
        lowcost[i]=a[i][1];//將與V1(或任意一點)有關的邊存入lowcost(與各點最小權值)
    }
    for (int i=1;i<n;i++)
    {
        minval=1000000;//初始化最小值為正無窮
        for (int j=1;j<=n;j++)
        {
                if (lowcost[j]>0&&lowcost[j]<minval)//如果當前權值不為0(即未連線過)且更小
                {
                    k=j;//記錄當前點
                    minval=lowcost[j];//將最小值存入
                }
        }
        ans+=minval;//統計最小生成樹最小權值和
        lowcost[k]=0;//標記該點
        for (int j=1;j<=n;j++)
        {
                if (lowcost[j]>0&&lowcost[j]>a[k][j])//由於U集合點增加,需更新與各點最小權值邊
                {
                    lowcost[j]=a[k][j];
                }
        }
    }

Part 2 \(Kruskal\)演算法

這個演算法就充分的利用了並查集.

思路

利用並查集的思想,在最初把所有的點的祖先定為自己,並將邊按全職大小從小到大排序,遍歷所有邊,如果兩端點的祖先不相同,則合併集合,\(ans\)加上邊權的大小,同時計數器\(++\) 等到計數器\(=n-1\)的時候結束迴圈,則很容易得到答案;

感性/理性證明

不確定是哪一種的證明方式

最小表示邊權最小,所以我們只要排序後找前\(n-1\)條邊,若有重邊或存在環等不滿足樹的行為,則去掉這條邊,加上第\(n\)條邊即可,最終的答案一點是最小的.

程式碼

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 500005
using namespace std;
struct node{
	int x,y,z;
}edge[maxn];//從x->y 的邊權為z的邊 
int fa[maxn],n,m,ans;
bool operator <(node x,node y)
{
	return x.z<y.z;
}
int get(int x)
{
	if(x==fa[x]) return x;
	return fa[x]=get(fa[x]);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].z);
	}
	int cnt=1;
	sort(edge+1,edge+m+1);
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=m;i++) 
	{
		if(cnt == n)
		{
            printf("%d\n",ans);
            return 0;
        }
		int x=get(edge[i].x),y=get(edge[i].y);
		if(x==y) continue;
		fa[x]=y;
		cnt+=1;
		ans+=edge[i].z;
	}
	if(cnt!=n) printf("orz");
	return 0;
}