【LeetCode】990 and 94(Union-Find算法详解,并查集算法)

本文详细介绍了并查集(Union-Find)算法,用于解决图论中的动态连通性问题。通过森林模型和数组实现,文章阐述了如何使用并查集进行节点连接、判断连通性以及计算连通分量。并结合实际例子,如等式方程的可满足性问题,展示了并查集的应用。同时,给出了优化算法的时间复杂度的思路。
摘要由CSDN通过智能技术生成

Union-Find算法详解

Union-Find 算法,也就是常说的并查集算法,主要是解决图论中「动态连通性」问题的。
一、问题介绍
动态连通性其实可以抽象成给⼀幅图连线。⽐如下⾯这幅图,总共有 10 个节点,他们互不相连,分别⽤ 0~9 标记:
在这里插入图片描述
Union-Find 算法主要需要实现这两个 API:

class UF:
	/* 将 p 和 q 连接 */
	def union(self, p, q);
	/* 判断 p 和 q 是否连通 */
	def connected(self, p, q);
	/* 返回图中有多少个连通分量 */
	def count();

这⾥所说的「连通」是⼀种等价关系,也就是说具有如下三个性质:

  1. ⾃反性:节点 p 和 p 是连通的。
  2. 对称性:如果节点 p 和 q 连通,那么 q 和 p 也连通。
  3. 传递性:如果节点 p 和 q 连通,q 和 r 连通,那么 p 和 r 也连通。

⽐如说之前那幅图,0~9 任意两个不同的点都不连通,调⽤ connected 都会返回 false,连通分量为 10个。
如果现在调⽤ union(0, 1),那么 0 和 1 被连通,连通分量降为 9 个。再调⽤ union(1, 2),这时 0,1,2 都被连通,调⽤ connected(0, 2) 也会返回true,连通分量变为 8个。
判断这种「等价关系」⾮常实⽤,⽐如说编译器判断同⼀个变量的不同引⽤,⽐如社交⽹络中的朋友圈计算等等。
这样,你应该⼤概明⽩什么是动态连通性了,那么⽤什么模型来表示这幅图的连通状态呢?⽤什么数据结构来实现代码呢?
二、基本思路
本文中,我们使⽤森林(若⼲棵树)来表示图的动态连通性,⽤数组来具体实现这个森林。
怎么⽤森林来表示连通性呢?我们设定树的每个节点有⼀个指针指向其⽗节点,如果是根节点的话,这个指针指向⾃⼰。⽐如说刚才那幅 10 个节点的图,⼀开始的时候没有相互连通,就是这样:
在这里插入图片描述
对于union函数,如果某两个节点被连通,则让其中的(任意)⼀个节点的根节点接到另⼀个节点的根节点上:
在这里插入图片描述
对于connected函数,如果节点 p 和 q 连通的话,它们⼀定拥有相同的根节点。因此我们需要新增一个API用于找寻节点所在子树的根节点,即Find函数,定义如下:

/* 返回某个节点 x 的根节点 */
def find(int x):
	// 根节点的 parent[x] == x
	while (parent[x] != x)
		x = parent[x];
	return x;

更新后的union和connected函数如下:

def union(int p, int q):
	rootP = find(p);
	rootQ = find(q);
	if (rootP == rootQ)
		return;
	// 将两棵树合并为⼀棵
	parent[rootP] = rootQ;
	// parent[rootQ] = rootP 也⼀样
	count--; // 两个分量合⼆为⼀

def connected(int p, int q):
	rootP = find(p);
	rootQ = find(q);
	return rootP == rootQ;

⾄此,Union-Find 算法就基本完成了。
后续关于该算法时间复杂度的优化可以参考UNION-FIND算法详解
下面,本文将从具体实例来对该算法进行实践。

990. 等式方程的可满足性

在这里插入图片描述
解法:并查集
我们可以将每一个变量看作图中的一个节点,把相等的关系 == 看作是连接两个节点的边,那么由于表示相等关系的等式方程具有传递性,即如果 a==b 和 b==c 成立,则 a==c 也成立。也就是说,所有相等的变量属于同一个连通分量。因此,我们可以使用并查集来维护这种连通分量的关系。具体步骤如下:

  1. 首先遍历所有的等式,构造并查集。同一个等式中的两个变量属于同一个连通分量,因此将两个变量进行合并。
  2. 然后遍历所有的不等式。同一个不等式中的两个变量不能属于同一个连通分量,因此对两个变量分别查找其所在的连通分量,如果两个变量在同一个连通分量中,则产生矛盾,返回 false。
  3. 如果遍历完所有的不等式没有发现矛盾,则返回 true。
class Solution:
    def equationsPossible(self, equations: List[str]) -> bool:
    	# 存储一棵树
        parents = [i for i in range(26)]
        # 记录树的重量(涉及时间复杂度的优化,可参考上文链接)
        size = [1 for _ in range(26)]
        # 将节点 p 和节点 q 连通
        def union(left, right):
            nonlocal parents
            nonlocal size
            rootA = find(left)
            rootB = find(right)
            # 已连通,则提前返回
            if rootA == rootB:
            	return
            sizeA = size[rootA]
            sizeB = size[rootB]
            
            # 小树接到大树下面,较平衡
            if sizeA < sizeB:
                parents[rootA] = rootB
                size[rootB] += sizeA
            else:
                parents[rootB] = rootA
                size[rootA] += sizeB
        # 返回节点node的连通分量根节点,根节点root的父节点是它本身,即parents[root] = root
        def find(node):
            x = ord(node) - ord("a")
            while x != parents[x]:
            	# 进行路径压缩,涉及时间复杂度优化,可以参考上文中链接
                parents[x] = parents[parents[x]]
                x = parents[x]
            
            return x
        # 遍历一次,构造并查集
        for equation in equations:
            if "==" in equation:
                h, t = equation.split("==")
                union(h, t)
        # print(parents)
        # 遍历第二次,进行正误性判断
        for equation in equations:
            if "==" not in equation:
                h, t = equation.split("!=")
                if find(h) == find(t):
                    return False
        return True

94. 二叉树的中序遍历

在这里插入图片描述
在这里插入图片描述
解法:递归
利用之前文章所讲述的二叉树遍历框架即可求解。

class Solution:
    def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        result = []
        def traverse(node):
            nonlocal result
            if not node:
                return 
            # 前序遍历位置
            traverse(node.left)
            # 中序遍历位置
            result.append(node.val)
            traverse(node.right)
            # 后序遍历位置

        traverse(root)
        return result
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值