圖---並查集和最小生成樹Kruskal算法


並查集(Union-find Sets)是一種非常精巧而實用的數據結構,它主要用於處理一些不相交集合的合並問題。

並查集的實現原理也比較簡單,就是使用樹來表示集合,樹的每個節點就表示集合中的一個元素,樹根對應的元素就是該集合的代表。

並查集實現

並查集的基本操作有三個:

  1. 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
	}
}
  1. find(x):找到元素 x 所在的集合的代表,該操作也可以用於判斷兩個元素是否位於同一個集合,只要將它們各自的代表比較一下就可以了。

路徑壓縮:為了加快查找速度,查找時將x到根節點路徑上的所有點的parent設為根節點。

  1. 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
}
用途
  1. 維護無向圖的連通性,判斷兩個點是否在同一個連通塊
  2. 判斷新增一條邊,是否會產生環
  3. 計算最后有多少個不相交的集合,既計算有多少個根節點(parent[i]==i)

最小生成樹

由於最小生成樹本身是一棵生成樹,所以需要時刻滿足以下兩點:

  • 生成樹中任意頂點之間有且僅有一條通路,也就是說,生成樹中不能存在回路;
  • 對於具有 n 個頂點的連通網,其生成樹中只能有 n-1 條邊,這 n-1 條邊連通着 n 個頂點。

連接 n 個頂點在不產生回路的情況下,只需要 n-1 條邊。

Kruskal算法

將所有邊按照權值的大小進行升序排序,然后從小到大一一判斷。條件為:如果這個邊不會與之前選擇的所有邊組成回路,就可以作為最小生成樹的一部分;反之,舍去。直到具有 n 個頂點的連通網篩選出來 n-1 條邊為止。篩選出來的邊和所有的頂點構成此連通網的最小生成樹。

從這里就可以看出,並查集就完全可以用於鑒定兩個頂點相連是否會產生回路。

題目樣例

1584. 連接所有點的最小費用

給你一個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
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM