並查集
並查集處理的是集合之間的關系,即‘union' , 'find' 。在這種數據類型中,N個不同元素被分成若干個組,每組是一個集合,這種集合叫做分離集合。並查集支持查找一個元素所屬的集合和兩個元素分別所屬的集合的合並。
並查集支持以下操作:
MAKE(X):建立一個僅有成員X的新集合。
UNION(X,Y):將包含X和Y的動態集合合並為一個新集合S,此后該二元素處於同一集合。
FIND(X):返回一個包含X的集合。
注意:並查集只能進行合並操作,不能進行分割操作。
並查集的實現原理
並查集是使用樹結構實現的:
1.初始化:准備N個節點來表示N個元素,最開始沒有邊。
2.合並:從一個組的根向另一個組的根連邊,這樣兩棵樹就變為了一棵樹,也就把兩個組合並為一個組了。
3.查詢:查詢兩個節點是否在同一個組,只需要查詢他們是否具有相同的根。
注意:
(1) 為避免樹的退化,對於每棵樹,記錄其高度rank。
(2) 如果合並時兩棵樹高度不同,那么從rank小的向rank大的連邊。
(3) 對於每個節點,一旦向上走到了一次根節點,就把這個節點到父親的邊改為直接連向根。不僅指查詢的節點,也包括查詢過程中經過的節點。使用這種簡化的方法時,即使樹的高度發生了改變,我們也不修改rank值。
並查集的復雜度
對N個元素並查集進行的一次操作的復雜度是O(α(N)),α(N)是阿克曼函數的反函數。這個復雜度是比O(LogN)還要快的。
並查集的具體C++實現:
#pragma once #include <cstring> const int MAX_N = 100000; class unite_find_set { private: int par[MAX_N]; int rank[MAX_N];//增加rank變量來防止樹的退化(盡量讓層數低的樹連接到層數高的樹上) public: unite_find_set(int n = 0) { init(n); } void init(int n) { for (int i = 0;i < n;++i) { par[i] = i; rank[i] = 0; } } int find(int x) { if (par[x] == x) return x; //此部分為兩部分,find(par[x])為回溯尋找根節點,par[x]=回溯結果是把 //葉子節點直接連接到根節點上以實現路徑壓縮,為簡化起見,路徑壓縮后 //我們沒有更改rank值 else return par[x] = find(par[x]); } void unite(int x, int y) { x = find(x); y = find(y); if (x == y) return; if (rank[x] < rank[y]) par[x] = y; else { par[y] = x; if (rank[x] == rank[y]) rank[x]++; } } bool same(int x, int y) { return find(x) == find(y); } }
並查集的應用實例
(因為本人太懶,以下代碼並未輸入數據測試,但我有迷之自信沒有問題)
更新:果然是迷之自信,已經發現問題了,已修復。
Kruskal最小生成樹生成法:
Kruskal算法的簡述見Prime算法的簡述這篇文章有簡單說明。
#include <algorithm> #include "union_find_set.h" const int MAX_V = 100; const int INF = 1000000; int cost[MAX_V][MAX_V]; int d[MAX_V], V; struct Edge { int from, to, cost; } edge[MAX_N];//用結構體表示邊 bool compare(const Edge &a, const Edge &b) { return a.cost < b.cost; } bool path[MAX_V][MAX_V];//記錄結果 int res; void Kruskal() { res = 0; union_find_set set(V);//初始化並查集 int p = 0;//初始化edge數組游標 for (int i = 0;i < V;++i) { for (int j = 0;j < V;++j) { path[i][j] = false;//給結果數組賦值 if (cost[i][j] != INF) { edge[p++] = { i,j,cost[i][j] };//給edge數組賦值 } } } std::sort(edge, edge + p, compare);//按邊權從小到大排序edge數組 for (int i = 0;i < p;++i) { if (!set.same(edge[i].from, edge[i].to))//如果邊的首尾節點沒有在一個生成樹中 { path[edge[i].from][edge[i].to] = true;//添加這條邊進入生成樹 set.unite(edge[i].from, edge[i].to);//讓首尾節點連接 res += edge[i].cost; } }