alg-in-go-1:动态连通性问题

前言:
有本算法书叫: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]
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值