算法第四版 Algorithms Part 1动态联通性

联通性检测用途

  • 照片中的像素
  • 网络中的计算机
  • 社交网络中的朋友
  • 计算机芯片中的电路元器件
  • 数学集合中的元素
  • Fortan程序中的变量
  • 复合体系中的金属位
    在这里插入图片描述

假定已连接等价于

  • 反身的: p与p本身是连接的.
  • 对称的: 如果p连接到q,那么q也链接到p
  • 传递的: 如果p连接到q并且q连接到r,那么p连接到r.

在这里插入图片描述

连接的组成

在这里插入图片描述

Union-find数据类型(API)

在这里插入图片描述
目标:为union-find设计有效的数据结构

  • 对象N的数量可能是巨大的
  • 操作次数M可能是巨大的
  • 查找操作和union链接操作可能是混合在一起的
type UF struct
func (uf UF)NewUF(N int) 使用N个对象(0-N-1)初始化union-find数据结构
func union(p, q int) 在p和q之间添加连接
func connected(p, q int) 判断p和q是否相连

动态连接客户端

  • 从标准输入stdin中读取输入
  • 重复:
    1.从标准输入获取整数对
    2.如果他们不相连, 连接他们并打印这一对数字
package main

import (
	"bufio"
	"os"
	"strconv"
	"strings"

	"github.com/iqer/algo4_go/algs4"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Scan()

	n, _ := strconv.Atoi(scanner.Text())
	uf := algs4.NewUF(n)
	for scanner.Scan() {
		line := scanner.Text()
		values := strings.Split(line, " ")
		p, _ := strconv.Atoi(values[0])
		q, _ := strconv.Atoi(values[1])
		uf.Union(p, q)
	}
}

quick-find 一种贪心算法

在这里插入图片描述
不同的点只有当在数组中的项是一样的时候, 那么他们是连通的.
校验p和q是否有相同的id
在这里插入图片描述

quick-find的实现在这里插入图片描述

func NewUF(n int) *UF {
	uf := UF{n: n}
	uf.id = make([]int, n)
	// 将读取到的数字存在于一个数组中
	// 每个索引就代表着一个数
	// 索引对应的值就是连通的点的索引值
	// 之后如果不同索引位置上的值相同, 就代表这些索引位置代表的点是连通的
	for i := 0; i < n; i++ {
		uf.id[i] = i
	}
	return &uf
}

func (uf *UF) connected(p, q int) bool {
	return uf.id[p] == uf.id[q]
}

func (uf *UF) Union(p, q int) {
	pid := uf.id[p]
	qid := uf.id[q]
	for i := 0; i < len(uf.id); i++ {
		if uf.id[i] == pid {
			uf.id[i] = qid
		}
	}
}

分析算法效率

算法初始化union操作find查找操作
快速查找O(N)O(N)1

尝试一种快速合并的算法

一种’懒的方法’, 尽量避免计算直到不得不进行计算
在这里插入图片描述
依然使用数组, 不过将其看作一组树即一片森林的表示.
Find查找操作就变成寻找两者是否拥有同样的parent节点, 如果有就是相连的.
这样做调整时, 只需要将p的根节点指向q的根节点, 就可以了.

func (uf *UF) root(i int) int {
	for i != uf.id[i] {
		i = uf.id[i]
	}
	return i
}

func (uf *UF) connected(p, q int) bool {
	return uf.id[p] == uf.id[q]
}

func (uf *UF) Union(p, q int)  {
	i := uf.root(p)
	j := uf.root(q)
	uf.id[i] = j
}

分析对比一下 quick-union还是太慢了

算法初始化union操作find查找操作
quick-findO(N)O(N)1
quick-uniono(N)O(N)O(N)

quick-find劣势

  • Union操作代价高昂(N维路径)
  • 树时平的, 但维持他们保持平的代价很高

quick-union劣势

  • 树变高了
  • 查找操作复杂度很高(可能是N维操作)

对quick-union的改进

改进方式1: 权重

在这里插入图片描述
避免将更大的树放在低位
在这里插入图片描述
上图对比普通的quick-union操作和带权重的union操作, 可以看到带权重的操作对树高有所控制.

package algs4

type UF struct {
	id   []int
	n    int
	size map[int]int
}

func NewUF(n int) *UF {
	uf := UF{n: n}
	uf.id = make([]int, n)
	uf.size = map[int]int{}
	// 将读取到的数字存在于一个数组中
	// 每个索引就代表着一个数
	// 索引对应的值就是连通的点的索引值
	// 之后如果不同索引位置上的值相同, 就代表这些索引位置代表的点是连通的
	for i := 0; i < n; i++ {
		uf.id[i] = i
		uf.size[i] = 1
	}
	return &uf
}

func (uf *UF) root(i int) int {
	for i != uf.id[i] {
		i = uf.id[i]
	}
	return i
}

func (uf *UF) connected(p, q int) bool {
	return uf.root(p) == uf.root(q)
}

// improved quick-union

func (uf *UF) Union(p, q int) {
	i := uf.root(p)
	j := uf.root(q)
	if i == j {
		return
	}
	if uf.size[i] < uf.size[j] {
		uf.id[i] = j
		uf.size[j] += uf.size[i]
	} else {
		uf.id[j] = i
		uf.size[i] += uf.size[j]
	}
}

在这里插入图片描述
树高在logN

分析带权重的quick-union

算法初始化union操作find查找操作
quick-findO(N)O(N)1
quick-uniono(N)O(N)O(N)
带权重的QUNlogNlogN

继续的改进方式2: path compression路径压缩

func (uf *UF) root(i int) int {
	for i != uf.id[i] {
		// 路径压缩, 使得每一个节点挂到它的祖父辈节点下
		uf.id[i] = uf.id[uf.id[i]]
		i = uf.id[i]
	}
	return i
}

算法性能对比

算法最差情况耗时
quick-findMN
quick-unionMN
weighted QUN+MlogN
QU+path compressionN+MlogN
weighted QU + path compressionN+MlogN

Union-Find的应用场景

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值