日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

最小生成树之克鲁斯卡尔(Kruskal)算法

發(fā)布時(shí)間:2025/6/17 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 最小生成树之克鲁斯卡尔(Kruskal)算法 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

學(xué)習(xí)最小生成樹(shù)算法之前我們先來(lái)了解下 下面這些概念:

樹(shù)(Tree):如果一個(gè)無(wú)向連通圖中不存在回路,則這種圖稱為樹(shù)。

生成樹(shù) (Spanning Tree):無(wú)向連通圖G的一個(gè)子圖如果是一顆包含G的所有頂點(diǎn)的樹(shù),則該子圖稱為G的生成樹(shù)。

生成樹(shù)是連通圖的極小連通子圖。這里所謂極小是指:若在樹(shù)中任意增加一條邊,則將出現(xiàn)一條回路;若去掉一條邊,將會(huì)使之變成非連通圖。

最小生成樹(shù)(Minimum Spanning Tree,MST):或者稱為最小代價(jià)樹(shù)Minimum-cost Spanning Tree:對(duì)無(wú)向連通圖的生成樹(shù),各邊的權(quán)值總和稱為生成樹(shù)的權(quán),權(quán)最小的生成樹(shù)稱為最小生成樹(shù)。

構(gòu)成生成樹(shù)的準(zhǔn)則有三條:

<1> 必須只使用該網(wǎng)絡(luò)中的邊來(lái)構(gòu)造最小生成樹(shù)。

<2> 必須使用且僅使用n-1條邊來(lái)連接網(wǎng)絡(luò)中的n個(gè)頂點(diǎn)

<3> 不能使用產(chǎn)生回路的邊。

?

構(gòu)造最小生成樹(shù)的算法主要有:克魯斯卡爾(Kruskal)算法和普利姆(Prim)算法他們都遵循以上準(zhǔn)則。

接下分別討論一下這兩種算法以及判定最小生成樹(shù)是否唯一的方法。

克魯斯卡爾算法

克魯斯卡爾算法的基本思想是以邊為主導(dǎo)地位,始終選擇當(dāng)前可用(所選的邊不能構(gòu)成回路)的最小權(quán)植邊。所以Kruskal算法的第一步是給所有的邊按照從小到大的順序排序。這一步可以直接使用庫(kù)函數(shù)qsort或者sort。接下來(lái)從小到大依次考察每一條邊(u,v)。

具體實(shí)現(xiàn)過(guò)程如下:

<1> 設(shè)一個(gè)有n個(gè)頂點(diǎn)的連通網(wǎng)絡(luò)為G(V,E),最初先構(gòu)造一個(gè)只有n個(gè)頂點(diǎn),沒(méi)有邊的非連通圖T={V,空},圖中每個(gè)頂點(diǎn)自成一格連通分量。

<2> 在E中選擇一條具有最小權(quán)植的邊時(shí),若該邊的兩個(gè)頂點(diǎn)落在不同的連通分量上,則將此邊加入到T中;否則,即這條邊的兩個(gè)頂點(diǎn)落到同一連通分量 ? ? ?上,則將此邊舍去(此后永不選用這條邊),重新選擇一條權(quán)植最小的邊。

<3> 如此重復(fù)下去,直到所有頂點(diǎn)在同一連通分量上為止。

下面是偽代碼:

1 // 把所有邊排序,記第i小的邊為e[i] (1<=i<=m)m為邊的個(gè)數(shù) 2 // 初始化MST為空 3 // 初始化連通分量,使每個(gè)點(diǎn)各自成為一個(gè)獨(dú)立的連通分量 4 5 for (int i = 0; i < m; i++) 6 { 7 if (e[i].u和e[i].v不在同一連通分量) 8 { 9 // 把邊e[i]加入MST 10 // 合并e[i].u和e[i].v所在的連通分量 11 } 12 }

上面的偽代碼,最關(guān)鍵的地方在于“連通分量的查詢合并”,需要知道任意兩個(gè)點(diǎn)是否在同一連通分量中,還需要合并兩個(gè)連通分量。

這個(gè)問(wèn)題正好可以用并查集完美的解決(不得不佩服前輩們聰明才智啊!)

并查集(Union-Find set)這個(gè)數(shù)據(jù)結(jié)構(gòu)可以方便快速的解決這個(gè)問(wèn)題。基本的處理思想是:初始時(shí)把每個(gè)對(duì)象看作是一個(gè)單元素集合;然后依次按順序讀入聯(lián)通邊,將連通邊中的兩個(gè)元素合并。在此過(guò)程中將重復(fù)使用一個(gè)搜索(Find)運(yùn)算,確定一個(gè)集合在那個(gè)集合中。當(dāng)讀入一個(gè)連通邊(u,v)時(shí),先判斷u和v是否在同一個(gè)集合中,如果是則不用合并;如果不是,則用一個(gè)合并(Union)運(yùn)算把u、v所在集合合并,使得這兩個(gè)集合中的任意兩個(gè)元素都連通。因此并查集在處理時(shí),主要用到搜索合并兩個(gè)運(yùn)算。

為了方便并查集的描述與實(shí)現(xiàn),通常把先后加入到一個(gè)集合中的元素表示成一個(gè)樹(shù)結(jié)構(gòu),并用根結(jié)點(diǎn)的序號(hào)來(lái)表示這個(gè)集合。因此定義一個(gè)parent[n]的數(shù)組,parent[i]中存放的就是結(jié)點(diǎn)i所在的樹(shù)中結(jié)點(diǎn)i的父親節(jié)點(diǎn)的序號(hào)。例如,如果parent[4]=5,就是說(shuō)4號(hào)結(jié)點(diǎn)的父親結(jié)點(diǎn)是5號(hào)結(jié)點(diǎn)。約定:如果i的父結(jié)點(diǎn)(即parent[i])是負(fù)數(shù),則表示結(jié)點(diǎn)i就是它所在的集合的根結(jié)點(diǎn),因?yàn)榧现袥](méi)有結(jié)點(diǎn)的序號(hào)是負(fù)的;并且用負(fù)數(shù)的絕對(duì)值作為這個(gè)集合中所含結(jié)點(diǎn)的個(gè)數(shù)。例如,如果parent[7]=-4,說(shuō)明7號(hào)結(jié)點(diǎn)就是它所在集合的根結(jié)點(diǎn),這個(gè)集合有四個(gè)元素。初始時(shí)結(jié)點(diǎn)的parent值為-1(每個(gè)結(jié)點(diǎn)都是根結(jié)點(diǎn),只包含它自己一個(gè)元素)。

實(shí)現(xiàn)Kruskal算法數(shù)據(jù)結(jié)構(gòu)主要有3個(gè)函數(shù)。

1 void UFset() // 初始化 2 { 3 for (int i = 0; i < n; i ++) 4 parent[i] = -1; 5 } 6 int Find(int x) // 查找并返回結(jié)點(diǎn)x所屬集合的根結(jié)點(diǎn) 7 { 8 int s; // 查找位置 9 for (s = x; parent[s]>=0; s = parent[s]); // 注意這里的 ; 10 while (s != x) // 優(yōu)化方案 -- 壓縮路徑,使后續(xù)的查找 11 { 12 int tmp = parent[x]; 13 parent[x] = s; 14 x = tmp; 15 } 16 return s; 17 } 18 // R1和R2是兩個(gè)元素,屬于兩個(gè)不同的集合,現(xiàn)在合并這兩個(gè)集合 19 void Union (int R1, int R2) 20 { 21 // r1位R1的根結(jié)點(diǎn),r2位R2的根結(jié)點(diǎn) 22 int r1 = Find(R1), r2 = Find(R2); 23 int tmp = parent[r1] + parent[r2]; // 兩個(gè)集合的結(jié)點(diǎn)個(gè)數(shù)之和(負(fù)數(shù)) 24 // 如果R2所在樹(shù)結(jié)點(diǎn)個(gè)數(shù) > R1所在樹(shù)結(jié)點(diǎn)個(gè)數(shù) 25 // 注意parent[r1]和parent[r2]都是負(fù)數(shù) 26 if(parent[r1] > parent[r2]) // 優(yōu)化方案 -- 加權(quán)法則 27 { 28 parent[r1] = r2; // 將根結(jié)點(diǎn)r1所在的樹(shù)作為r2的子樹(shù)(合并) 29 parent[r2] = tmp; // 跟新根結(jié)點(diǎn)r2的parent[]值 30 } 31 else 32 { 33 parent[r2] = r1; // 將根結(jié)點(diǎn)r2所在的樹(shù)作為r1的子樹(shù)(合并) 34 parent[r1] = tmp; // 跟新根結(jié)點(diǎn)r1的parent[]值 35 } 36 }

接下來(lái)對(duì) Find 函數(shù)和 Union 函數(shù)的實(shí)現(xiàn)過(guò)程作詳細(xì)解釋。

Find 函數(shù):在 Find 函數(shù)中如果僅僅靠一個(gè)循環(huán)來(lái)直接得到結(jié)點(diǎn)所屬集合的根結(jié)點(diǎn)的話,通過(guò)多次的 Union 操作就會(huì)有很多結(jié)點(diǎn)在樹(shù)的比較深層次中,再查找起來(lái)就會(huì)很費(fèi)時(shí)。可以通過(guò)壓縮路徑來(lái)加快后續(xù)的查找速度:增加一個(gè) While 循環(huán),每次都把從結(jié)點(diǎn) x 到集合根結(jié)點(diǎn)的路徑上經(jīng)過(guò)的結(jié)點(diǎn)直接設(shè)置為根結(jié)點(diǎn)的子女結(jié)點(diǎn)。雖然這增加了時(shí)間,但以后的查找會(huì)更快。如圖 3.4 所示,假設(shè)從結(jié)點(diǎn) x = 6 開(kāi)始?jí)嚎s路徑,則從結(jié)點(diǎn) 6 到根結(jié)點(diǎn)1 的路徑上有 3 個(gè)結(jié)點(diǎn):6、10、8,壓縮后,這 3 個(gè)結(jié)點(diǎn)都直接成為根結(jié)點(diǎn)的子女結(jié)點(diǎn),如圖(b)所示。

?

并查集:Find函數(shù)中的路徑壓縮

Union 函數(shù):兩個(gè)集合并時(shí),任一方可做為另一方的子孫。怎樣來(lái)處理呢,現(xiàn)在一般采用加權(quán)合并,把兩個(gè)集合中元素個(gè)數(shù)少的根結(jié)點(diǎn)做為元素個(gè)數(shù)多的根結(jié)點(diǎn)的子女結(jié)點(diǎn)。這樣處理有什么優(yōu)勢(shì)呢?直觀上看,可以減少樹(shù)中的深層元素的個(gè)數(shù),減少后續(xù)查找時(shí)間。

例如,假設(shè)從 1 開(kāi)始到 n,不斷合并第 i 個(gè)結(jié)點(diǎn)與第 i+1 個(gè)結(jié)點(diǎn),采用加權(quán)合并思路的過(guò)程如下圖所示(各子樹(shù)根結(jié)點(diǎn)上方的數(shù)字為其 parent[ ]值)。這樣查找任一結(jié)點(diǎn)所屬集合的時(shí)間復(fù)雜度幾乎都是 O(1)!!!

?

并查集:加權(quán)合并

不用加權(quán)規(guī)則可能會(huì)得到下圖所示的結(jié)果。這就是典型的退化樹(shù)(只有一個(gè)葉結(jié)點(diǎn),且每個(gè)非葉結(jié)點(diǎn)只有一個(gè)子結(jié)點(diǎn))現(xiàn)象,再查找起來(lái)就會(huì)很費(fèi)時(shí),例如查找結(jié)點(diǎn) n 的根結(jié)點(diǎn)時(shí)復(fù)雜度為 O(n)。

?

并查集:合并時(shí)不加權(quán)的結(jié)果。

例 利用 Kruskal 算法求無(wú)向網(wǎng)的最小生成樹(shù),并輸出依次選擇的各條邊及最終求得的最小生成樹(shù)的權(quán)。

假設(shè)數(shù)據(jù)輸入時(shí)采用如下的格式進(jìn)行輸入:首先輸入頂點(diǎn)個(gè)數(shù) n 和邊數(shù) m,然后輸入 m 條邊的數(shù)據(jù)。每條邊的數(shù)據(jù)格式為:u v w,分別表示這條邊的兩個(gè)頂點(diǎn)及邊上的權(quán)值。頂點(diǎn)序號(hào)從 1開(kāi)始計(jì)起。

分析:

在下面的代碼中,首先讀入邊的信息,存放到數(shù)組 edges[ ]中,并按權(quán)值從小到大進(jìn)行排序。

Kruskal( )函數(shù)用于實(shí)現(xiàn) :首先初始化并查集,然后從 edges[ ]數(shù)組中依次選用每條邊,如果這條邊的兩個(gè)頂點(diǎn)位于同一個(gè)連通分量,則要棄用這條邊;否則合并這兩個(gè)頂點(diǎn)所在的連通分量。

代碼如下:

1 #include <stdio.h> 2 #include <string.h> 3 #include <algorithm> 4 #define MAXN 11 //頂點(diǎn)個(gè)數(shù)的最大值 5 #define MAXM 20 //邊的個(gè)數(shù)的最大值 6 using namespace std; 7 8 struct edge // 9 { 10 int u, v, w; //邊的頂點(diǎn)、權(quán)值 11 }edges[MAXM]; //邊的數(shù)組 12 13 int parent[MAXN]; //parent[i]為頂點(diǎn) i 所在集合對(duì)應(yīng)的樹(shù)中的根結(jié)點(diǎn) 14 int n, m; //頂點(diǎn)個(gè)數(shù)、邊的個(gè)數(shù) 15 int i, j; //循環(huán)變量 16 void UFset( ) //初始化 17 { 18 for( i=1; i<=n; i++ ) 19 parent[i] = -1; 20 } 21 int Find( int x ) //查找并返回節(jié)點(diǎn) x 所屬集合的根結(jié)點(diǎn) 22 { 23 int s; //查找位置 24 for( s=x; parent[s]>=0; s=parent[s] ); 25 while( s!=x ) //優(yōu)化方案―壓縮路徑,使后續(xù)的查找操作加速。 26 { 27 int tmp = parent[x]; 28 parent[x] = s; 29 x = tmp; 30 } 31 return s; 32 } 33 34 //將兩個(gè)不同集合的元素進(jìn)行合并,使兩個(gè)集合中任兩個(gè)元素都連通 35 void Union( int R1, int R2 ) 36 { 37 int r1 = Find(R1), r2 = Find(R2); //r1 為 R1 的根結(jié)點(diǎn),r2 為 R2 的根結(jié)點(diǎn) 38 int tmp = parent[r1] + parent[r2]; //兩個(gè)集合結(jié)點(diǎn)個(gè)數(shù)之和(負(fù)數(shù)) 39 //如果 R2 所在樹(shù)結(jié)點(diǎn)個(gè)數(shù) > R1 所在樹(shù)結(jié)點(diǎn)個(gè)數(shù)(注意 parent[r1]是負(fù)數(shù)) 40 if( parent[r1] > parent[r2] ) //優(yōu)化方案――加權(quán)法則 41 { 42 parent[r1] = r2; 43 parent[r2] = tmp; 44 } 45 else 46 { 47 parent[r2] = r1; 48 parent[r1] = tmp; 49 } 50 } 51 bool cmp( edge a, edge b ) //實(shí)現(xiàn)從小到大排序的比較函數(shù) 52 { 53 return a.w <= b.w; 54 } 55 void Kruskal( ) 56 { 57 int sumweight = 0; //生成樹(shù)的權(quán)值 58 int num = 0; //已選用的邊的數(shù)目 59 int u, v; //選用邊的兩個(gè)頂點(diǎn) 60 UFset( ); //初始化 parent[]數(shù)組 61 for( i=0; i<m; i++ ) 62 { 63 u = edges[i].u; v = edges[i].v; 64 if( Find(u) != Find(v) ) 65 { 66 printf( "%d %d %d\n", u, v, edges[i].w ); 67 sumweight += edges[i].w; num++; 68 Union( u, v ); 69 } 70 if( num>=n-1 ) break; 71 } 72 printf( "weight of MST is %d\n", sumweight ); 73 } 74 int main( ) 75 { 76 int u, v, w; //邊的起點(diǎn)和終點(diǎn)及權(quán)值 77 scanf( "%d%d", &n, &m ); //讀入頂點(diǎn)個(gè)數(shù) n 78 for( int i=0; i<m; i++ ) 79 { 80 scanf( "%d%d%d", &u, &v, &w ); //讀入邊的起點(diǎn)和終點(diǎn) 81 edges[i].u = u; edges[i].v = v; edges[i].w = w; 82 } 83 sort(edges,edges+m,cmp); 84 Kruskal(); 85 return 0; 86 } View Code

轉(zhuǎn)載于:https://www.cnblogs.com/yoke/p/6697013.html

總結(jié)

以上是生活随笔為你收集整理的最小生成树之克鲁斯卡尔(Kruskal)算法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。