最小生成樹


最小生成樹\((MST)\)

閑談

原因

又是蒟蒻的一篇為了記憶寫的博客,扎實知識點。

背景

很多圖論的題目會首先要求我們將圖轉化成樹狀結構再進行一系列的操作,而在這篇文章當中我將會介紹一種最常見的算法\(Kruskal\)來解決最小生成樹這個問題。

\(Kruskal\)

背景

\(Kruskal\)作為最經典的最小生成樹算法,在稍微改動后也可以求出最大生成樹。是由\(Joseph Bernard Kruskal\)發明的,它的時間復雜度為\(O(elog_2e)\),適合疏松圖的最小生成樹。

分析

\(Kruskal\)的思路為:假設連通網\(G=(V,E)\),令最小生成樹的初始狀態為只有\(n\)個頂點而無邊的非連通圖\(T=(V,{})\),圖中每個頂點自成一個連通分量。在\(E\)中選擇代價最小的邊,若該邊依附的頂點分別在\(T\)中不同的連通分量上,則將此邊加入到\(T\)中;否則,舍去此邊而選擇下一條代價最小的邊。依此類推,直至\(T\)中所有頂點構成一個連通分量為止。我們用比較正常的方法來(翻譯)一遍。首先我們需要一個前置技巧:並查集。

並查集

並查集並不是本篇文章的重點,只將簡單介紹它的用法和基本思想。並查集是一種可以快速的求出每個節點所在的集合的數據結構,我們首先用數組\(fa[N]\)來記錄它隸屬哪個集合,第一步是初始化,將每一個節點的所在集合定義為它自己\(fa[i] = i\)我們用一個\(find\)函數來進行查找的工作

int find(int x){
	if(x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}

通過這個操作我們就可以求出它所在的集合了,如果想要將兩個節點所在的集合合並的話,那么就將第一個節點的\(fa\)值設為第二個節點的值即可。

我們回歸到正題\(Kruskal\)\(Kruskal\)算法是根據邊的權值用遞增的方式,一次找出邊權值最小的邊來建立最小生成樹,並且每次所添加的邊不能造成生成樹的回路,這就需要我們剛才提到的並查集來做到了,直到找到第\(N - 1\)個邊為止。
我們來看一個具體例子來更好的理解

我們首先將圖中所有的邊權快排。
首先將最短的邊\((1, 3)\)加入生成樹中

第二步將\((3, 4)\)加入生成樹中

第三步將\((3, 5)\)加入生成樹中

注意接下來的一部非常關鍵,需要我們之前提到的並查集來實現,我們發現如果按照邊權來添加的話,應該添加的邊為\((4, 5)\)但是我們發現\(4\)\(5\)已經都被加入到生成樹當中了,所以我們將其跳過添加\((4, 2)\)

這時,我們已經建立了\(4\)條邊,程序結束。
在進行每次加邊時,我們需要考慮

\[1、它是否是剩下的邊中最小的 2、加入它是否會生成環\]

代碼實現

#include<cstdio>
#include<iostream>
#include<algorithm>
#define N 100010
using namespace std;
int n, m, ans = 0, cnt = 0;
int head[N], fa[N];
struct edge{
	int u, v, w;
}e[N << 1];
inline int read(){
	int s = 0;
	char ch = getchar();
	while(ch < '0' || ch > '9') ch = getchar();
	while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s;
}
bool cmp(edge a, edge b){return a.w < b.w;}
int find(int x){	//!!!非常重要,並查集模板 
	if(x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
void Kruskal(){	//Kruskal模板 
	sort(e + 1, e + m + 1, cmp);
	for(int i = 1; i <= n; i++) fa[i] = i;
	for(int i = 1; i <= m; i++){
		int x = find(e[i].u), y = find(e[i].v);
		if(x == y) continue;	//如果兩條邊已經聯通那么跳過 
		ans += e[i].w;
		fa[x] = y;	//將兩個點合並 
		if(++cnt == n - 1) break; //當n - 1條邊時結束 
	}
}
int main(){
	n = read(); m = read();
	for(int i = 1; i <= m; i++){
		int x, y, z; 
		x = read(); y = read(); z = read();
		e[i].u = x; e[i].v = y; e[i].w = z;
	}
	Kruskal();
	printf("%d\n", ans);
	return 0;
}

我們來模擬一遍這個圖。

很明顯,得出了正確的答案。那么這就是\(Kruskal\)算法,我們要靈活的應用它,有時我們需要求最大生成樹,我們只要反着排序就可以了。
完結撒花ヾ(✿゚▽゚)ノ


免責聲明!

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



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