prim算法求最小生成树_克鲁斯卡尔算法(Kruskal算法)求最小生成树
上一節介紹了求最小生成樹之普里姆算法。該算法從頂點的角度為出發點,時間復雜度為O(n2),更適合與解決邊的綢密度更高的連通網。本節所介紹的克魯斯卡爾算法,從邊的角度求網的最小生成樹,時間復雜度為O(eloge)。和普里姆算法恰恰相反,更適合于求邊稀疏的網的最小生成樹。對于任意一個連通網的最小生成樹來說,在要求總的權值最小的情況下,最直接的想法就是將連通網中的所有邊按照權值大小進行升序排序,從小到大依次選擇。由于最小生成樹本身是一棵生成樹,所以需要時刻滿足以下兩點:
生成樹中任意頂點之間有且僅有一條通路,也就是說,生成樹中不能存在回路;
對于具有 n 個頂點的連通網,其生成樹中只能有 n-1 條邊,這 n-1 條邊連通著 n 個頂點。
連接 n 個頂點在不產生回路的情況下,只需要 n-1 條邊。
所以克魯斯卡爾算法的具體思路是:將所有邊按照權值的大小進行升序排序,然后從小到大一一判斷,條件為:如果這個邊不會與之前選擇的所有邊組成回路,就可以作為最小生成樹的一部分;反之,舍去。直到具有 n 個頂點的連通網篩選出來 n-1 條邊為止。篩選出來的邊和所有的頂點構成此連通網的最小生成樹。
判斷是否會產生回路的方法為:在初始狀態下給每個頂點賦予不同的標記,對于遍歷過程的每條邊,其都有兩個頂點,判斷這兩個頂點的標記是否一致,如果一致,說明它們本身就處在一棵樹中,如果繼續連接就會產生回路;如果不一致,說明它們之間還沒有任何關系,可以連接。
假設遍歷到一條由頂點 A 和 B 構成的邊,而頂點 A 和頂點 B 標記不同,此時不僅需要將頂點 A 的標記更新為頂點 B 的標記,還需要更改所有和頂點 A 標記相同的頂點的標記,全部改為頂點 B 的標記。
圖 1 連通網
例如,使用克魯斯卡爾算法找圖 1 的最小生成樹的過程為:首先,在初始狀態下,對各頂點賦予不同的標記(用顏色區別),如下圖所示:
(1)
對所有邊按照權值的大小進行排序,按照從小到大的順序進行判斷,首先是(1,3),由于頂點 1 和頂點 3 標記不同,所以可以構成生成樹的一部分,遍歷所有頂點,將與頂點 3 標記相同的全部更改為頂點 1 的標記,如(2)所示:
(2)
其次是(4,6)邊,兩頂點標記不同,所以可以構成生成樹的一部分,更新所有頂點的標記為:
(3)
其次是(2,5)邊,兩頂點標記不同,可以構成生成樹的一部分,更新所有頂點的標記為:
(4)
然后最小的是(3,6)邊,兩者標記不同,可以連接,遍歷所有頂點,將與頂點 6 標記相同的所有頂點的標記更改為頂點 1 的標記:
(5)
繼續選擇權值最小的邊,此時會發現,權值為 5 的邊有 3 個,其中(1,4)和(3,4)各自兩頂點的標記一樣,如果連接會產生回路,所以舍去,而(2,3)標記不一樣,可以選擇,將所有與頂點 2 標記相同的頂點的標記全部改為同頂點 3 相同的標記:
(6)
當選取的邊的數量相比與頂點的數量小 1 時,說明最小生成樹已經生成。所以最終采用克魯斯卡爾算法得到的最小生成樹為(6)所示。實現代碼:
#include "stdio.h"#include "stdlib.h"#define MAX_VERtEX_NUM 20#define VertexType inttypedef struct edge{ VertexType initial; VertexType end; VertexType weight;}edge[MAX_VERtEX_NUM];//定義輔助數組typedef struct { VertexType value;//頂點數據 int sign;//每個頂點所屬的集合}assist[MAX_VERtEX_NUM];assist assists;//qsort排序函數中使用,使edges結構體中的邊按照權值大小升序排序int cmp(const void *a,const void*b){ return ((struct edge*)a)->weight-((struct edge*)b)->weight;}//初始化連通網void CreateUDN(edge *edges,int *vexnum,int *arcnum){ printf("輸入連通網的邊數:\n"); scanf("%d %d",&(*vexnum),&(*arcnum)); printf("輸入連通網的頂點:\n"); for (int i=0; i scanf("%d",&(assists[i].value)); assists[i].sign=i; } printf("輸入各邊的起始點和終點及權重:\n"); for (int i=0 ; i scanf("%d,%d,%d",&(*edges)[i].initial,&(*edges)[i].end,&(*edges)[i].weight); }}//在assists數組中找到頂點point對應的位置下標int Locatevex(int vexnum,int point){ for (int i=0; i if (assists[i].value==point) { return i; } } return -1;}int main(){ int arcnum,vexnum; edge edges; CreateUDN(&edges,&vexnum,&arcnum); //對連通網中的所有邊進行升序排序,結果仍保存在edges數組中 qsort(edges, arcnum, sizeof(edges[0]), cmp); //創建一個空的結構體數組,用于存放最小生成樹 edge minTree; //設置一個用于記錄最小生成樹中邊的數量的常量 int num=0; //遍歷所有的邊 for (int i=0; i //找到邊的起始頂點和結束頂點在數組assists中的位置 int initial=Locatevex(vexnum, edges[i].initial); int end=Locatevex(vexnum, edges[i].end); //如果頂點位置存在且頂點的標記不同,說明不在一個集合中,不會產生回路 if (initial!=-1&& end!=-1&&assists[initial].sign!=assists[end].sign) { //記錄該邊,作為最小生成樹的組成部分 minTree[num]=edges[i]; //計數+1 num++; //將新加入生成樹的頂點標記全不更改為一樣的 for (int k=0; k if (assists[k].sign==assists[end].sign) { assists[k].sign=assists[initial].sign; } } //如果選擇的邊的數量和頂點數相差1,證明最小生成樹已經形成,退出循環 if (num==vexnum-1) { break; } } } //輸出語句 for (int i=0; i-1; i++) { printf("%d,%d\n",minTree[i].initial,minTree[i].end); } return 0;}測試數據:
輸入連通網的邊數:
6 10
輸入連通網的頂點:
1
2
3
4
5
6
輸入各邊的起始點和終點及權重:
1,2,6
1,3,1
1,4,5
2,3,5
2,5,3
3,4,5
3,5,6
3,6,4
4,6,2
5,6,61,3
4,6
2,5
3,6
2,3
總結
以上是生活随笔為你收集整理的prim算法求最小生成树_克鲁斯卡尔算法(Kruskal算法)求最小生成树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 公众号点击图片变成另一张_微信公众号点击
- 下一篇: 树复制替换id_程序员的进阶课-架构师之