並查集(Union-find Sets)是一種非常精巧而實用的數據結構,它主要用於處理一些不相交集合的合並問題。
並查集的實現原理也比較簡單,就是使用樹來表示集合,樹的每個節點就表示集合中的一個元素,樹根對應的元素就是該集合的代表。
並查集實現
並查集的基本操作有三個:
makeSet(s)
:建立一個新的並查集,其中包含 s 個單元素集合。
parent := make([]int, len(edges)+1)
var makeSet func(nums []int)
makeSet = func(nums []int) {
for i := range nums {
nums[i] = i
}
}
find(x)
:找到元素 x 所在的集合的代表,該操作也可以用於判斷兩個元素是否位於同一個集合,只要將它們各自的代表比較一下就可以了。
路徑壓縮:為了加快查找速度,查找時將x到根節點路徑上的所有點的parent設為根節點。
unionSet(x, y)
:把元素 x 和元素 y 所在的集合合並,要求 x 和 y 所在的集合不相交,如果相交則不合並。
按秩合並:使用秩來表示樹的高度,在合並時,總是將具有較小秩的樹根指向具有較大秩的樹根。簡單的說,就是總是將比較矮的樹作為子樹,添加到較高的樹中。
為了保存秩,需要額外使用一個與 parent
同長度的數組rank
,並將所有元素都初始化為 0。
rank:= make([]int,size)
var union func(x, y int) bool
union = func(x, y int) bool {
x, y = find(x), find(y)
if x == y {
return false
}
if rank[x] > rank[y] {
parent[y] = x
} else {
parent[x] = y
//提高秩等級
if rank[x] == rank[y] {
rank[y]++
}
}
return true
}
用途
- 維護無向圖的連通性,判斷兩個點是否在同一個連通塊
- 判斷新增一條邊,是否會產生環
- 計算最后有多少個不相交的集合,既計算有多少個根節點(
parent[i]==i
)
最小生成樹
由於最小生成樹本身是一棵生成樹,所以需要時刻滿足以下兩點:
- 生成樹中任意頂點之間有且僅有一條通路,也就是說,生成樹中不能存在回路;
- 對於具有 n 個頂點的連通網,其生成樹中只能有 n-1 條邊,這 n-1 條邊連通着 n 個頂點。
連接 n 個頂點在不產生回路的情況下,只需要 n-1 條邊。
Kruskal算法
將所有邊按照權值的大小進行升序排序,然后從小到大一一判斷。條件為:如果這個邊不會與之前選擇的所有邊組成回路,就可以作為最小生成樹的一部分;反之,舍去。直到具有 n 個頂點的連通網篩選出來 n-1 條邊為止。篩選出來的邊和所有的頂點構成此連通網的最小生成樹。
從這里就可以看出,並查集就完全可以用於鑒定兩個頂點相連是否會產生回路。
題目樣例
給你一個points
數組,表示 2D 平面上的一些點,其中 points[i] = [xi, yi]
。
連接點 [xi, yi]
和點 [xj, yj]
的費用為它們之間的 曼哈頓距離 :|xi - xj| + |yi - yj|
,其中 |val|
表示 val
的絕對值。
請你返回將所有點連接的最小總費用。只有任意兩點之間 有且僅有 一條簡單路徑時,才認為所有點都已連接。
輸入:points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
輸出:20
解釋:我們可以按照上圖所示連接所有點得到最小總費用,總費用為20。注意到任意兩個點之間只有唯一一條路徑互相到達。
func minCostConnectPoints(points [][]int) int {
//並查集
n := len(points)
parent := make([]int, n)
for i := range points {
parent[i] = i
}
var find func(x int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
var union func(x, y int) bool
union = func(x, y int) bool {
x, y = find(x), find(y)
if x == y {
return false
}
parent[x] = y
return true
}
//循環遍歷所有點,生成所有可能的邊,並按距離從小到大排序
type edge struct{ v, w, dis int }
edges := make([]edge, 0)
for i := range points {
for j := i + 1; j < n; j++ {
edges = append(edges, edge{i, j, getDistance(points[i][0], points[i][1], points[j][0], points[j][1])})
}
}
sort.Slice(edges, func(i, j int) bool {
return edges[i].dis < edges[j].dis
})
//循環遍歷所有的邊,直到所有的點都已經連接在一起,就是最小生成樹
ans := 0
for _, p := range edges {
if union(p.v, p.w) {
n--
ans += p.dis
if n == 0 {
break
}
}
}
return ans
}
func getDistance(x1, y1, x2, y2 int) int {
if x1 > x2 {
x1, x2 = x2, x1
}
if y1 > y2 {
y1, y2 = y2, y1
}
return x2 - x1 + y2 - y1
}