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 v1和v2,進行判斷。
- 如果 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
測試結果如圖: