資料結構之Kruskal演算法(並查集的應用)
Kruskal演算法基本思想
假設G=(V,E)是連通圖,將G中的邊按權值從小到大的順序排列
1、將n個頂點看成n個集合
2、按權值從大到小的順序選擇邊,所選邊應滿足兩個頂點不在同一個頂點集合內,即加入此邊後不會在生成樹中產生迴路,將該邊放到生成樹邊的集合中。同時將該邊的兩個頂點所在的頂點集合合併。
3、重複2,直到所有的頂點都在同一個頂點集合內。
舉個例子
1、首先比較圖中的所有邊的權值,找到最小的權值的邊(1,6),加入生成樹的邊集中,TE={(1,6)}
2、比較圖中其餘邊的權值,找到最小的權值的邊(3,5),且加入此邊後不會使TE產生迴路,TE={(1,6),(3,5)}
3、比較圖中其餘邊的權值,找到最小的權值的邊(2,4),且加入此邊後不會使TE產生迴路,TE={(1,6),(3,5),(2,4)}
4、比較圖中其餘邊的權值,找到最小的權值的邊(5,6),且加入此邊後不會使TE產生迴路,TE={(1,6),(3,5),(2,4),(5,6)}
5、繼續上述演算法,找到邊(1,3),但加入後將會使得TE產生迴路,故舍棄。再找另外一條權值最小的邊,找到邊(3,6),但加入後也會使得TE產生迴路,故舍棄繼續尋找。找到最小權值邊(2,6)滿足條件,故將(2,6)加入TE中,此時TE={(1,6),(3,5),(2,4),(5,6),(2,6)}
現在所生成的最小生成樹中已經有了n-1條邊。此演算法完成。
並查集並查集—-百度百科 參考勇幸|Thinking的部落格
並查集:(union-find sets)
一種簡單的用途廣泛的集合. 並查集是若干個不相交集合,能夠實現較快的合併和判斷元素所在集合的操作,應用很多,如其求無向圖的連通分量個數等。最完美的應用當屬:實現Kruskar演算法求最小生成樹。
並查集的精髓(即它的三種操作,結合實現程式碼模板進行理解):
1、Make_Set(x) 把每一個元素初始化為一個集合
初始化後每一個元素的父親節點是它本身,每一個元素的祖先節點也是它本身(也可以根據情況而變)。
2、Find_Set(x) 查詢一個元素所在的集合
查詢一個元素所在的集合,其精髓是找到這個元素所在集合的祖先!這個才是並查集判斷和合並的最終依據。判斷兩個元素是否屬於同一集合,只要看他們所在集合的祖先是否相同即可。
合併兩個集合,也是使一個集合的祖先成為另一個集合的祖先,具體見示意圖
3、Union(x,y) 合併x,y所在的兩個集合
合併兩個不相交集合操作很簡單:
利用Find_Set找到其中兩個集合的祖先,將一個集合的祖先指向另一個集合的祖先。
並查集的優化
1、Find_Set(x)時 路徑壓縮
尋找祖先時我們一般採用遞迴查詢,但是當元素很多亦或是整棵樹變為一條鏈時,每次Find_Set(x)都是O(n)的複雜度,有沒有辦法減小這個複雜度呢?
答案是肯定的,這就是路徑壓縮,即當我們經過”遞推”找到祖先節點後,”回溯”的時候順便將它的子孫節點都直接指向祖先,這樣以後再次Find_Set(x)時複雜度就變成O(1)了,如下圖所示;可見,路徑壓縮方便了以後的查詢。
2、Union(x,y)時 按秩合併
即合併的時候將元素少的集合合併到元素多的集合中,這樣合併之後樹的高度會相對較小。
主要程式碼
//建立一個新的集合,每一個子節點就是一個數,本身就是他的根節點
void Make_Set(int x)
{
father[x] = x;
R[x] = 0;
}
//通過遞迴向上查詢根節點,回溯時改變當前節點的父節點,直接指向根節點。
int Find_Set(int x)
{
if(x != father[x])
father[x] = Find_set(father[x]);
return father[x];
}
//將根節點設定為-1的非遞迴方法
int Find_Set2(int x)
{
int y = x;
while(y!= -1)
y = father[y];
return y;
}
//兩個集合的合併演算法
void Union(int x, int y)
{
int GrandX = Find_set(x);
int GrandY = Find_set(y);
if(GrandX == GrandY)
return;
if(R[GrandX] < R[GrandY])
father[GrandX] = GrandY;
else
{
if(R[GrandX] == R[GrandY])
R[GrandX]++;
father[GrandY] = GrandX;
}
}
kruskal演算法完整版程式碼
#include<stdio.h>
#define max 50
typedef struct
{
int bv,ev,w;
} edges;
edges edgeset[max];
int createdgeset()
{
int arcnum,i;
printf("\n input the undigraph: ");
scanf("%d",&arcnum);
for(i=1;i<=arcnum;i++)
{
printf("bv,ev,w=");
scanf("%d,%d,%d",&edgeset[i].bv,&edgeset[i].ev,&edgeset[i].w);
}
return arcnum;
}
void sort(int n)
{
int i,j;
edges t;
for(i=1;i<=n-1;i++)
for(j=i+1;j<=n;j++)
if(edgeset[i].w>edgeset[j].w)
{
t=edgeset[j];
edgeset[j]=edgeset[i];
edgeset[i]=t;
}
}
int seeks(int set[],int v)
{
int i=v;
while(set[i]>0)
i=set[i];
return i;
}
void kruskal(int e)
{
int set[max],v1,v2,i;
printf("kruskal 's spanning tree:\n");
for(i=1;i<=max;i++)
set[i]=0;
i=0;
while(i<e)
{
v1=seeks(set,edgeset[i].bv);
v2=seeks(set,edgeset[i].ev);
if(v1!=v2)
{
printf("(%d,%d)%d\n",edgeset[i].bv,edgeset[i].ev,edgeset[i].w);
set[v1]=v2;
}
i++;
}
}
int main()
{
int i,arcnum;
arcnum=createdgeset();
sort(arcnum);
printf("the arcnum from little to big:");
printf("\nbv ev w\n");
for(i=1;i<=arcnum;i++)
printf("%d %d %d\n",edgeset[i].bv,edgeset[i].ev,edgeset[i].w);
kruskal(arcnum);
return 0;
}
執行截圖
總結
kruskal演算法相比於prim演算法而言,要簡單不少,prim演算法多適用於頂點多的稠密圖,而kruskal演算法多適用於邊數多的稀疏圖。kruskal演算法其精髓在於運用了並查集的概念,並查集是一個好東西,有時間要好好研究一下它的應用
相關文章
- 演算法與資料結構之並查集演算法資料結構並查集
- 資料結構 — 並查集的原理與應用資料結構並查集
- 資料結構之並查集資料結構並查集
- 資料結構-並查集資料結構並查集
- 優雅的資料結構–並查集資料結構並查集
- 【資料結構】帶權並查集資料結構並查集
- 資料結構:速通並查集資料結構並查集
- 並查集(二)並查集的演算法應用案例上並查集演算法
- 並查集應用總結並查集
- 資料結構——並查集 學習筆記資料結構並查集筆記
- 並查集的應用並查集
- 並查集是一種怎樣的資料結構?並查集資料結構
- 並查集應用並查集
- 並查集—應用並查集
- 並查集的應用2並查集
- 資料結構與演算法知識點總結(3)樹、圖與並查集資料結構演算法並查集
- 並查集深度應用並查集
- 並查集以及應用並查集
- 資料結構與演算法——克魯斯卡爾(Kruskal)演算法資料結構演算法
- 【演算法】並查集的運用演算法並查集
- 【資料結構基礎應用】【查詢和排序演算法】資料結構排序演算法
- 並查集的簡單應用並查集
- 並查集的分析及應用並查集
- 並查集的應用:hdu 1213並查集
- 社交網路 (並查集的應用)並查集
- 並查集詳解與應用並查集
- 並查集擴充套件應用並查集套件
- 食物鏈(並查集的簡單應用)並查集
- 並查集演算法Union-Find的思想、實現以及應用並查集演算法
- 並查集演算法並查集演算法
- 前端資料結構(1)之棧及其應用前端資料結構
- 【從蛋殼到滿天飛】JS 資料結構解析和演算法實現-並查集(二)JS資料結構演算法並查集
- 並查集經典應用場景並查集
- 【學習筆記】並查集應用筆記並查集
- 前端資料結構(3)之連結串列及其應用前端資料結構
- 並查集的初級應用及進階並查集
- 資料結構與演算法 | 棧的實現及應用資料結構演算法
- 資料結構和演算法之——二分查詢上資料結構演算法