算法-并查集

0. 写在前面的小结

并查集的功能

  1. 将两个元素添加到一个集合中join(node1, node2)
  2. 判断两个元素是否在同一个集合、连通分量中find(node1) == find(node2)

基本思想

  • 每个集合、连通分量都通过一个元素(根节点)来代表。
  • 通过维护father[]数组来实现
  • 路径压缩:在find()的过程中,如果处于第三层,可以将3的祖先改为1,从而降低了树的高度。
  • 路径压缩后的find()的时间复杂度从O(logn)变为O(1)
        1       路径压缩                   1
    2           ------->              2       3
3

1. 寻找图中是否存在路径(LeetCode 1971)

有一个具有 n 个顶点的双向图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。
图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。
每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。

请你确定是否存在从顶点source开始,到顶点destination结束的有效路径
如果存在有效路径返回 true,否则返回 false 。

# 并查集
class UnionFindSet:
    # 初始化并查集
    def __init__(self, n):
        self.father = list(range(n))
    
    # 寻找本联通分量的根
    def find(self, node1:int) -> int:
        if node1 == self.father[node1]:
            return node1
        else:
            # 路径压缩
            self.father[node1] = self.find(self.father[node1])
            return self.father[node1]

    def join(self, node1: int, node2: int):
        root1 = self.find(node1)
        root2 = self.find(node2)
        if root1 == root2:
            return
        if root1 != root2:
            self.father[root2] = root1

class Solution:
    def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
        ufs = UnionFindSet(n)

        for x, y in edges:
            ufs.join(x, y)
        
        return ufs.find(source) == ufs.find(destination)

2. 冗余连接 (LeetCode 684)

树可以看成是一个连通且无环的无向图。
给定往一棵n 个节点 (节点值 1~n) 的树中添加一条边后的图
添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。
图的信息记录于长度为n的二维数组edges,edges[i] = [ai, bi]表示图中在aibi之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着n个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。

# 并查集
class UnionFindSet:
    # 初始化并查集
    def __init__(self, n):
        self.father = list(range(n))
    
    # 寻找本联通分量的根
    def find(self, node1:int) -> int:
        if node1 == self.father[node1]:
            return node1
        else:
            # 路径压缩
            self.father[node1] = self.find(self.father[node1])
            return self.father[node1]

    def join(self, node1: int, node2: int):
        root1 = self.find(node1)
        root2 = self.find(node2)
        if root1 == root2:
            return
        if root1 != root2:
            self.father[root2] = root1

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        ufs = UnionFindSet(len(edges)+1)

        for x, y in edges:
            # 已经连通了,还要添加的边为冗余边
            if ufs.find(x) == ufs.find(y):
                return [x,y]
            else:
                ufs.join(x, y)

3. 冗余连接II (LeetCode 685)(有难度)

有三种情况:

  1. 存在两条入度为2的边,且删除任一条,剩下来的都构成树,那么删除后面那条
  2. 存在两条入度为2的边,必须删除指定的一条,才能构成树
  3. 存在有向环,此类情况同冗余连接 I
# 并查集
class UnionFindSet:
    # 初始化并查集
    def __init__(self, n):
        self.father = list(range(n))
    
    # 寻找本联通分量的根
    def find(self, node1:int) -> int:
        if node1 != self.father[node1]:
            self.father[node1] = self.find(self.father[node1])
        return self.father[node1]

    def join(self, node1: int, node2: int):
        root1 = self.find(node1)
        root2 = self.find(node2)
        if root1 != root2:
            self.father[root2] = root1


class Solution:
    def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
        # 使用并查集,判断删一条边之后是否为树
        def isTreeAfterRemoveEdge(edges, deleteEdge):
            # 创建并查集
            ufs = UnionFindSet(len(edges) + 1)
            for x, y in edges:
                if [x, y] == deleteEdge:
                    continue
                if ufs.find(x) == ufs.find(y):
                    return False
                ufs.join(x, y)
            return True

        # 使用并查集,移除有向环中的冗余边
        def removeCycleEdge(edges):
            # 创建并查集
            ufs = UnionFindSet(len(edges) + 1)
            for x, y in edges:
                if ufs.find(x) == ufs.find(y):
                    return [x, y]
                ufs.join(x, y)
            return None


        # 记录每个顶点的入度
        indegrees = [0] * (len(edges) + 1)
        for x, y in edges:
            indegrees[y] += 1

        # 记录入度为2的顶点关联的边(最多2条)
        edgeStore = []

        # n-1 ~ 0
        for i in range(len(edges)-1, -1, -1):
            if indegrees[edges[i][1]] == 2:
                edgeStore.append(edges[i])
        
        # 情况1,2:存在两条入度为2的边
        if edgeStore:
            # 优先删排在后面的边
            if isTreeAfterRemoveEdge(edges, edgeStore[0]):
                return edgeStore[0]
            else:
                return edgeStore[1]
        
        # 情况3:没有入度为2的边,存在有向环
        return removeCycleEdge(edges)
posted @ 2024-10-31 13:06  Frank23  阅读(10)  评论(0编辑  收藏  举报