Kruskal算法和Prim算法相比,就是Kruskal算法從邊出發,不斷尋找當前未添加進Et的、且權值最小的邊,若添加後不形成環,則添加成功;否則跳過,繼續嘗試添加下一條邊。最後,判斷邊的數量arcnum是否是點的數量vexnum-1,若是則最小生成樹構造成功,否則失敗。
Prim算法與頂點相關時間複雜度O(|V|²),所以適合頂點少邊多的圖;
Kruskal反之,算法與邊相關,時間複雜度爲O(|E|log|E|),所以適合邊少頂點多的圖;
首先Vt中沒有邊,直接選取權值最小的邊,即V1--V3,之後V3的parent更新,更新爲parent[3] = 1,表明頂點V3的根節點爲V1;
(c)、(d)、(e)圖同理,現在到(f),而目前權值最小的是5,有V1--V4,V3--V4,V2--V3這三條邊,選擇哪條呢?如果添加前面兩條,則會形成環,這不符合最小生成樹,所以只能添加V1--V4這條邊
關鍵是如何判斷是否形成了環呢?這就需要用到並查集的知識了。這裏的條件是,若一條邊的2個頂點,它們通過並查集查詢到的根節點若相同,則判定形成了環。
什麼是並查集呢?並查集是一種樹形結構,又叫“不相交集合”,保持了一組不相交的動態集合,每個集合通過一個代表來識別,代表即集合中的某個成員,通常選擇根做這個代表。
這裏到(d)圖時,V3的根節點是V1,V5的根節點是V2,V6的根節點是V4。從(d) --> (e),連接了V3--V6,查詢V3的根節點是V1,查詢V6的根節點是V4,因此更新parent[4] = 1
後面同理
#include <iostream>
#include <algorithm>
using namespace std;
// Kruskal算法只與邊有關,故這裏只存儲邊的關係
// 並規定vstart爲編號小的頂點,vend爲編號大的頂點
typedef struct{
int vstart;
int vend;
int weight;
}Edge;
// 按照權值,從小到大排序
bool edgeCmp(const Edge& e1, const Edge& e2)
{
return e1.weight < e2.weight;
}
// 該圖由arcnum條邊組成
typedef struct{
int vexnum;
int arcnum;
vector<Edge> edge;
}Graph;
bool newGraph(Graph &g)
{
cout << "-------準備使用創建無向帶權圖-------" << endl;
cout << "請輸入頂點數和邊數(空格隔開): ";
cin >> g.vexnum >> g.arcnum;
// 圖可以沒有邊,但不能沒有頂點
// 若無向圖有n個頂點,則最多有n*(n-1)/2條邊(無向完全圖)
if( g.vexnum<0 || g.arcnum<=0 || g.arcnum>(g.vexnum*(g.vexnum-1)/2) ){
cerr << "數據輸入有誤,請檢查數據!" << endl;
g.vexnum = g.arcnum = 0;
return false;
}
cout << "請輸入" << g.arcnum << "條邊的起始頂點、終止頂點和權值(空格隔開)" << endl;
int vstart, vend, weight;
int n = g.arcnum;
while( n-- ){
cin >> vstart >> vend >> weight;
if( vstart<=0 || vend<=vstart || weight<=0 ){
cerr << "數據輸入有誤,請檢查數據!" << endl;
g.edge.clear();
return false;
}
g.edge.push_back( Edge{vstart,vend,weight} );
}
return true;
}
void printRes(vector<Edge>& Et, int cost)
{
cout << "當前最小生成樹的邊爲:";
for(auto e : Et )
cout << "V" + to_string(e.vstart)
<< "--"
<< "V" + to_string(e.vend) << ", ";
cout << endl << "當前最小生成樹的權值爲:" << cost << endl;
cout << endl;
}
// 查找結點Vi的根節點
int findRoot(int parent[], int i)
{
int root = i;
// 若等於,說明根爲本身,已經找到
while( root != parent[root] )
root = parent[root];
return root;
}
bool Kruskal(Graph &g)
{
vector<Edge> Et; //存儲目最小生成樹的邊
int cost = 0;
// 並查集的使用,我這裏定義數組下標i爲頂點Vi
// 數組的值parent[i]爲頂點Vi的父節點的下標
// 初始時Vt沒有邊,因此每個節點都是根節點
int parent[g.arcnum+1];
for(int i = 1; i <= g.arcnum; ++i)
parent[i] = i;
// 將所有邊的權值按照從小到大排序
sort(g.edge.begin(), g.edge.end(), edgeCmp);
for(int i = 0; i < g.arcnum; ++i)
{
// 判斷圖中是否有環,使用並查集查詢
// 若該邊起點,在並查集中的根節點
// 與該邊終點,在並查集中的根節點相同,則判定有環
int root1 = findRoot(parent, g.edge[i].vstart);
int root2 = findRoot(parent, g.edge[i].vend);
// 不形成環
if( root1 != root2 ){
// 更新權值
cost += g.edge[i].weight;
// 更新並查集
parent[root2] = root1;
// 更新Vt
Et.push_back(g.edge[i]);
printRes(Et, cost);
}
// 形成環,則忽略此邊
}
// 最後判斷邊和頂點數量關係,看是否構成了最小生成樹
if( g.vexnum-1 != Et.size() ){
cerr << "此圖不能構造最小生成樹!" << endl;
return false;
}
return true;
}
int main()
{
Graph g;
if( newGraph(g) ){
Kruskal(g);
}
return 0;
}