1. 程式人生 > >最小生成樹kruskal演算法(貪心+並查集+堆優化)

最小生成樹kruskal演算法(貪心+並查集+堆優化)

kruskal演算法

克魯斯卡爾演算法的基本思想是以邊為主導地位,始終選擇當前可用(所選的邊不能構成迴路)的最小權植邊。所以Kruskal演算法的第一步是給所有的邊按照從小到大的順序排序。這一步可以直接使用庫函式qsort或者sort。接下來從小到大依次考察每一條邊(u,v)。

具體實現過程如下:

<1> 設一個有n個頂點的連通網路為G(V,E),最初先構造一個只有n個頂點,沒有邊的非連通圖T={V,空},圖中每個頂點自成一格連通分量。

<2> 在E中選擇一條具有最小權植的邊時,若該邊的兩個頂點落在不同的連通分量上,則將此邊加入到T中;否則,即這條邊的兩個頂點落到同一連通分量上,則將此邊捨去(此後永不選用這條邊),重新選擇一條權植最小的邊。

<3> 如此重複下去,直到所有頂點在同一連通分量上為止。

簡單來說就是挑最小邊,加入當前的“樹”中,而挑最小邊可以遍歷,時間複雜度是O(n),如果用最小堆時間複雜度可以達到o(logn)

併入當前樹中又不會形成環路解決方法當然就是用並查集

並查集(Union-Find set)這個資料結構可以方便快速的解決這個問題。基本的處理思想是:初始時把每個物件看作是一個單元素集合;然後依次按順序讀入聯通邊,將連通邊中的兩個元素合併。在此過程中將重複使用一個搜尋(Findroot)運算,確定一個集合在那個集合中。當讀入一個連通邊(u,v)時,先判斷u和v是否在同一個集合中,如果是則不用合併;如果不是,則用一個合併

(Union)運算把u、v所在集合合併,使得這兩個集合中的任意兩個元素都連通。因此並查集在處理時,主要用到搜尋合併兩個運算。

為了方便並查集的描述與實現,通常把先後加入到一個集合中的元素表示成一個樹結構,並用根結點的序號來表示這個集合。因此定義一個uf[n]的陣列,uf[i]中存放的就是結點i所在的樹中結點i的父親節點的序號。例如,如果uf[4]=5,就是說4號結點的父親結點是5號結點。約定:如果i的父結點(即uf[i])是負數,則表示結點i就是它所在的集合的根結點,因為集合中沒有結點的序號是負的;並且用負數的絕對值作為這個集合中所含結點的個數。例如,如uf[7]=-4,說明7號結點就是它所在集合的根結點,這個集合有四個元素。初始時結點的值為-1(每個結點都是根結點,只包含它自己一個元素)。

#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;

#define inf 99999
#define maxsize 10


typedef struct Graph//定義圖 a,b,代表邊的兩頂點,w表權值 
{
	int a,b;
	int w;
}G;

int nv,ne;//圖的點、邊 
int uf[maxsize];//並查集陣列 
G minheap[maxsize];//最小堆陣列 
int size=0;



void insert(G t)//向最小堆中插入元素 
{
	int i=++size;
	for(;minheap[i/2].w>t.w;i/=2)
		minheap[i]=minheap[i/2];
	minheap[i]=t;
}

void down()//下濾 
{
	int ch;
	for(int pa=1;pa*2<=size;pa=ch)
	{
		ch=pa*2;
		if(ch!=size&&minheap[ch].w>minheap[ch+1].w)
			ch=ch+1;
		if(minheap[ch].w<minheap[pa].w)
			swap(minheap[ch],minheap[pa]);
		else
			break;
	}
}

G del()//從最小堆中刪除元素 
{
	if(size>0)
	{
		G mx=minheap[1];
		minheap[1]=minheap[size--];
		down();
		return mx;
	}
}

int findroot(int u)//發現根 
{
	int i;
	for( i=u;uf[i]!=-1;i=uf[i]);
	return i;
}

void Create()//建立最小堆 
{
	int a,b,weigh,k=0;
	G g;
	for(int i=1;i<=ne;i++)//直接輸入圖的邊及權值 
	{
		cin>>a>>b>>weigh;
		g.a=a;
		g.b=b;
		g.w=weigh;
		insert(g);//放入最小堆 
	}
	for(int i=0;i<=nv;i++)//初始化並查集 
		uf[i]=-1;
	minheap[0].a=minheap[0].b=0;
	minheap[0].w=-1;
}

int  kruskal()
{
	int a,b,sum=0;
	G tmp;
	for(int i=1;i<=ne;i++)
	{
		tmp=del();//每次從最小堆中找到一個最小的邊儲存並刪除
		a=findroot(tmp.a);//找到a的根 
		b=findroot(tmp.b);//找到b的根 
		if(a!=b)//合併相當於union函式
		{
			uf[b]=a;
			sum+=tmp.w;
		}
	}
	return sum;//返回最小生成樹的總權值 
}

int main()
{
	cin>>nv>>ne;
	Create();
	cout<<kruskal()<<endl;
	return 0;
}