Kruskal算法(~詳細整理,簡單易懂,附最全注釋代碼實現)


Kruskal算法

1、簡介

Kruskal算法是一種用來查找最小生成樹( M S T MST MST)的算法,由Joseph Kruskal在1956年發表。求最小生成樹的算法常用有兩種:Kruskal算法和Prim算法。這里指路一篇Prim算法的詳解blog:https://blog.csdn.net/hzf0701/article/details/107927858。與Prim算法不同的是,該算法的核心思想是歸並邊,而Prim算法的核心思想是歸並點。這里我們會在后面的實現過程中看到。

2、構造過程

假設連通網 N = ( V , E ) N=(V,E) N=(V,E),將 N N N中的邊按權值從小到大的順序排列。
初始狀態為只有 n n n個頂點而無邊的非連通圖 T = ( V , { } ) T=(V,\{\}) T=(V,{}),圖中每個頂點自成一個連通分量。
E E E中選擇權值最小的邊,若該邊依附的頂點落在 T T T中不同的連通分量上(即不形成回路),則將此邊將入到 T T T中,否則舍去此邊而選擇下一條權值最小的邊。
③重復②,直到 T T T中所有的頂點都在同一連通分量上為止。

這個算法的構造過程十分簡潔明了,那么為什么這樣的構造過程能否形成最小生成樹呢?我們來看第二個步驟,因為我們選取的邊的頂點是不同的連通分量,且邊權值是最小的,所以我們保證加入的邊都不使得 T T T有回路,且權值也最小。這樣最后當所有的連通分量都相同時,即所有的頂點都在生成樹中被連接成功了,我們構造成的樹也就是最小生成樹了。

3、示例

在這里插入圖片描述

4、算法實現

步驟:
①將存儲邊的數組temp按權值從小到大排序,注意進行運算符重載。
②初始化連通分量數組 v e r x verx verx
③依次查看數組temp的邊,循環執行以下操作。

  • 依次從排好序的數組temp中選出一條邊 ( u , v ) (u,v) (u,v)
  • v e r x verx verx中分別查找 u u u v v v所在的連通分量 v 1 和 v 2 v_1和v_2 v1v2,進行判斷。
    • 如果 v 1 v_1 v1 v 2 v_2 v2不等,說明所選的兩個頂點分別屬於不同的連通分量,則將此邊存入最小生成樹 t r e e tree tree,並合並 v 1 v_1 v1 v 2 v_2 v2這個兩個連通分量
    • 如果 v 1 v_1 v1 v 2 v_2 v2相等,則說明所選的兩個頂點屬於同一個連通分量,舍去此邊而選擇下一條權值最小的邊
/* *郵箱:unique_powerhouse@qq.com *blog:https://me.csdn.net/hzf0701 *注:文章若有任何問題請私信我或評論區留言,謝謝支持。 * */
#include<bits/stdc++.h> //POJ不支持

#define rep(i,a,n) for (int i=a;i<=n;i++)//i為循環變量,a為初始值,n為界限值,遞增
#define per(i,a,n) for (int i=a;i>=n;i--)//i為循環變量, a為初始值,n為界限值,遞減。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair

using namespace std;

const int inf = 0x3f3f3f3f;//無窮大
const int maxn = 1e5;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll>  pll;
typedef pair<int, int> pii;
//*******************************分割線,以上為自定義代碼模板***************************************//

struct edge{
	int s;//邊的起始頂點。
	int e;//邊的終端頂點。
	int w;//邊權值。
	bool operator < (const edge &a){
		return w<a.w;
	}
};
edge temp[maxn];//臨時數組存儲邊。
int verx[maxn];//輔助數組,判斷是否連通。
edge tree[maxn];//最小生成樹。
int n,m;//n*n的圖,m條邊。
int cnt;//統計生成結點個數,若不滿足n個,則生成失敗。
int sum;//最小生成樹權值總和。
void print(){
	//打印最小生成樹函數。
	cout<<"最小生成樹的權值總和為:"<<sum<<endl;
	rep(i,0,cnt-1){
		cout<<tree[i].s<<" "<<tree[i].e<<"邊權值為"<<tree[i].w<<endl;
	}
}
void Kruskal(){
	rep(i,1,n)
		verx[i]=i;//這里表示各頂點自成一個連通分量。
	cnt=0;sum=0;
	sort(temp,temp+m);//將邊按權值排列。
	int v1,v2;
	rep(i,0,m-1){
		v1=verx[temp[i].s];
		v2=verx[temp[i].e];
		if(v1!=v2){
			tree[cnt].s=temp[i].s;tree[cnt].e=temp[i].e;tree[cnt].w=temp[i].w;//並入最小生成樹。
			rep(j,1,n){
				//合並v1和v2的兩個分量,即兩個集合統一編號。
				if(verx[j]==v2)verx[j]=v1; //默認集合編號為v2的改為v1.
			}
			sum+=tree[cnt].w;
			cnt++;
		}
	}
	//結束雙層for循環之后得到tree即是最小生成樹。
	print();
}
int main(){
	//freopen("in.txt", "r", stdin);//提交的時候要注釋掉
	IOS;
	while(cin>>n>>m){
		int u,v,w;
		rep(i,0,m-1){
			cin>>u>>v>>w;
			temp[i].s=u;temp[i].e=v;temp[i].w=w;
		}
		Kruskal();
	}
	return 0;
}

5、算法分析

對於有 m m m條邊和 n n n個頂點的圖。在 f o r for for循環中最耗時的操作就是合並兩個不同的連通分量,第一個循環語句的頻度為 m m m,第二個循環由於存在 i f if if語句,所以平均頻度是 l o g 2 n log_2n log2n,所以該算法的平均時間復雜度為 O ( m l o g 2 n ) O(mlog_2n) O(mlog2n),故和Prim算法相比,此算法適合用於稀疏圖

6、測試

以示例數據為測試樣例:

7 11
1 2 7
1 4 5
2 4 9
2 3 8
2 5 7
4 5 15
4 6 6
6 7 11
5 6 8
5 7 9
3 5 5

測試結果如圖:
在這里插入圖片描述


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM