前言:
有本算法书叫:Algorithms 4th Edition.pdf
,它是用java实现的,但是算法的内核是一样,不在乎于语言,考虑到java当今的…, 咱们尝试用golang学习算法.
问题:
思考🤔:你会如何实现它?。。。。。下面看看the big bang theory
男主leonard
母校名校普林斯顿大学
是如何解决的。
第一版:使用一个数组保存每个节点,如果两个节点的值一样,那么就是联通着的:
品3秒,再继续:
等会?看上图,如果 0 5 两个节点已经连通了,它们的值都是 5,那么当后续5 6 两个节点联通时,最好是把节点6的值修改成和节点5一样,这样只需要修改一次值,但是这种理想的情况又需要额外的判断处理。
但是如果把节点5的值修改成和节点6一样,那么节点0也得跟着和节点5一样做处理,如果出现特殊的情况,假设0-8 共九个节点都是联通着的,它们的值都是8, 当把节点8和节点9 再去联通时,需要遍历0-8这九个节点,然后把它们的值都修改成节点9的值:即9.
其实修改值操作还好,赋个值罢了,问题在于,需要遍历整个数组去判断已经联通着的那批节点,找出它们,再修改值。所以是线性N操作。。。性能问题突出,如果节点数量大,那。。。
我们先假设节点数量不多,看看代码如何实现:
package main
import "fmt"
var id []int
func main() {
var n = 10
quickFindUF(n)
}
/*
*
第一个版本
*/
func quickFindUF(n int) {
id = make([]int, n)
for i := 0; i < n; i++ {
id[i] = i
}
union(0, 5)
union(5, 6)
union(1, 2)
union(2, 7)
union(3, 4)
union(3, 8)
union(4, 9)
// true
fmt.Println(connected(0, 6))
// false
fmt.Println(connected(5, 7))
// true
fmt.Println(connected(4, 9))
}
func connected(p, q int) bool {
return id[p] == id[q]
}
// 合并操作遍历了整个数组,性能不行...
func union(p, q int) {
pid := id[p]
qid := id[q]
for i := 0; i < len(id); i++ {
if id[i] == pid {
id[i] = qid
}
}
}
知道性能问题,我们往下升级优化:
版本2: 把联通性问题转变成🌲的结构问题:如果两个节点的根节点相同,那就是联通着的,妙,妙,妙不~
代码实现:
package main
import "fmt"
var id []int
func main() {
quickUnionUF(10)
}
func quickUnionUF(n int) {
id = make([]int, n)
for i := 0; i < n; i++ {
id[i] = i
}
union(0, 5)
union(5, 6)
union(1, 2)
union(2, 7)
union(3, 4)
union(3, 8)
union(4, 9)
fmt.Println(connected(0, 6))
fmt.Println(connected(5, 7))
fmt.Println(connected(4, 9))
}
func root(i int) int {
for i != id[i] {
i = id[i]
}
return i
}
func connected(p, q int) bool {
return root(p) == root(q)
}
func union(p, q int) {
i := root(p)
j := root(q)
if i == j {
return
}
id[i] = j
}
第一版和第二版的性能分析对比:
FYI: 树本来是当查询和新增达到性能的平衡,中庸之道,但是当形成的树变成了链表形式后,性能也成了O(N)了。。。
所以针对版本2要升级优化:
优化一:连通时,尽量让树扁平化:
通过用空间换时间,引入一个sz[]
: 记录每个节点(作为根节点时)有多少个元素以便可判断🌲的大小:连通时让小树挂在大树下;
优化二:root()
判断时,再次扁平化处理:把孙节点往上提一提:指向它的祖节点
完整代码如下:
package main
import "fmt"
var id []int
var sz []int
func main() {
quickUnionUF(10)
}
func quickUnionUF(n int) {
id = make([]int, n)
sz = make([]int, n)
for i := 0; i < n; i++ {
id[i] = i
sz[i] = 1
}
union(0, 5)
union(5, 6)
union(1, 2)
union(2, 7)
union(3, 4)
union(3, 8)
union(4, 9)
fmt.Println(connected(0, 6))
fmt.Println(connected(5, 7))
fmt.Println(connected(4, 9))
}
func root(i int) int {
for i != id[i] {
// 让路径的长度减半
id[i] = id[id[i]]
i = id[i]
}
return i
}
func connected(p, q int) bool {
return root(p) == root(q)
}
func union(p, q int) {
i := root(p)
j := root(q)
if i == j {
return
}
if sz[i] < sz[j] {
id[i] = j
sz[j] += sz[i]
} else {
id[j] = i
sz[i] += sz[j]
}
}