最小生成樹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是否在同一個集合中,如果是則不用合併;如果不是,則用一個合併
為了方便並查集的描述與實現,通常把先後加入到一個集合中的元素表示成一個樹結構,並用根結點的序號來表示這個集合。因此定義一個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;
}