前言
最小生成樹,對於一個無向圖,聯通且不含圈的圖稱爲樹。
給定無向圖G=(V,E),連接G中所有點,且邊集是E的子集的數稱爲G的生成樹(Spanning Tree),而權值最小的生成樹稱爲最小生成樹(Minimal Spanning Tree, MST)。構造MST的算法有很多,最常見的有兩個:Kruskal算法和Prim算法。這裏只介紹Kruskal算法,因爲這個算法編寫容易且效率很高。
前面描述不清楚的話,可以看下圖
這是一個無向圖,每條邊有權重,你可以採用幾條邊就能連接所有的點,就得到了一顆生成樹(前提是不含圈)。如果樹的所有邊的權值之和最小,就稱爲最小生成樹。
算法流程
Kruskal算法的流程非常簡單和直接:
- 1、對於邊集E,對所有的邊按照權值從小到大排序
- 2、 依次將排序好的邊放入MST中,如果加入這條邊(u,v)使圖出現了環,則放棄
- 3、直到所有的點都出現在MST中,構造完成。
其中第2步可能出現環,則需要檢驗環,這裏可以採用DFS或者BFS的方式進行圖遍歷(寫起來很複雜,需要判斷什麼樣纔算是環,而且複雜度很高)。所以我們採用另外一種方式來進行檢驗,那就是"並查集"。並查集可以查看我之前的博文並查集解釋。
另外第2步會出現的情況就是已經有幾個邊入選但是構不成樹,如下圖所示:
事實上我們並不需要馬上構成樹,我們只需要將邊選出來,然後構造點的集合就可以了,這也就是爲什麼要用並查集的另一個理由,例如上圖中有三個集合,分別是{4,6},{2,5},{1,3,}。如果連同了,那兩個集合就合併爲一個集合。其他還沒有邊連接的點構成另外的集合,例如{7},{8},{9}…
那並查集如何判斷環呢?其實只要判斷邊的兩個端點(u,v)是否在同一個集合中,如果在同一個集合中,說明已經聯通(構成樹),你再添加就會構成環。
以上,kruskal算法應該解釋清楚了。
C++模板
struct Edge{
int from, to, cost;
Edge(int from, int to, int cost){this->from = from; this->to = to; this->cost = cost;};
}; // 邊的結構體,其他可能有不同
bool cmp(Edge e1, Edge e2){return e1->cost<e2->cost;} // 比較函數
//以下是並查集
int pre[N]; //記錄每個點的父節點, N個節點
int unionsearch(int root)
{
int son, tmp;
son = root;
while(root != pre[root]) //尋找根節點
root = pre[root];
while(son != root) //已經找到了根節點,進行路徑壓縮
{
tmp = pre[son];
pre[son] = root;
son = tmp;
}
return root; //返回根節點
}
void join(int root1, int root2) //合併函數
{
int x, y;
x = unionsearch(root1);//找到根節點
y = unionsearch(root2);//找到根節點
if(x != y)
pre[x] = y; //合併,就是其中一個的父節點換爲另一個
}
Edge e[nmax]; // 默認已經讀取完畢,並且邊的個數就是nmax
int Kruskal()
{
int total_cost = 0;
int total_num = 0;
sort(e, e+nmax, cmp);
memset(pre, 0, sizeof(pre) * N);
for(int i = 0; i<nmax;i++)
{
int from = e[i]->from;
int to = e[i]->to;
int cost = e[i]->cost;
//節點還沒有加入過
if(!pre[from])
{
pre[from] = from;
total_num++; //節點數增加
}
if(!pre[to])
{
pre[to] = to;
total_num++; //節點數增加
}
if(unionsearch(from) != unionsearch(to))//這條邊兩個點不屬於同一個集合,那就合併
join(from, to);
else
continue;
total_cost += cost;
if(total_num==N) //全部加入,退出
break;
}
return total_cost;
}
條件有限就直接記事本敲了,如果有錯誤還請告知