第十一章 图论 Part5

并查集理论基础

思路

用一维数组来维护这样的结构,数组的索引表示某个值,数组的值表示该索引同一集合的根

# n 根据题目中的节点数量而定,一般比节点数量大一点就好
n = 1005
father = [0] * n  # Python 中列表的初始大小为 n,并用 0 填充

# 并查集初始化
def init():
    for i in range(n):
        father[i] = i

# 并查集里寻根的过程
def find(u):
    if u == father[u]:
        return u
    else:
        father[u] = find(father[u])  # 路径压缩
        return father[u]

# 判断 u 和 v 是否找到同一个根
def is_same(u, v):
    u = find(u)
    v = find(v)
    return u == v

# 将 v->u 这条边加入并查集
def join(u, v):
    u = find(u)  # 寻找 u 的根
    v = find(v)  # 寻找 v 的根
    if u == v:
        return  # 如果发现根相同,说明在一个集合,不用两个节点相连,直接返回
    father[v] = u

任务

1971. 寻找图中是否存在路径

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

请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径 。

给你数组 edges 和整数 n、source 和 destination,如果从 source 到 destination 存在 有效路径 ,则返回 true,否则返回 false 。

思路一

第一个想到的是dfs(之前解过求两个点之间的所有路径),由于是找是否存在,所以是有返回值的dfs,此时考虑类树形DP,下层告诉上层是否找到路径。

class Solution:
    def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
        # 将边集转化为邻接表
        graph = defaultdict(list)
        for a,b in edges:
            graph[a].append(b)
            graph[b].append(a)

        visited = [False] * n
        return self.dfs(graph,source,destination,visited)

# 有返回值的dfs,有点类似于树形DP
    def dfs(self,graph,start,end,visited):
        visited[start] = True
        if start == end: #终止条件在终点
            return True
        

        for i in graph[start]:
            if not visited[i]:
                if self.dfs(graph,i,end,visited): #如果下层返回True,则本层向上返回True,表示找到到达终点的路径
                    return True
        return False

思路二

本章所学的并查集正好对应这道题,只要在一条边上,就归为同一集合,连续的边的连接归为同一集合(该集合内的点都能到达),最后判断source和des是否为同一集合即可。

class Solution:
    def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
        unionFind = UnionFind(n)
        for i in range(len(edges)):
            unionFind.join(edges[i][0],edges[i][1])
        return unionFind.is_same(source,destination)
            
class UnionFind:
    def __init__(self,n):
        self.father = [0] * n
        for i in range(n):
            self.father[i] = i
        
    # (查) 
    def find(self,u): 
        if u == self.father[u]:
            return u
        else:
            self.father[u] = self.find(self.father[u])  # 路径压缩(一直像上层返回最底层的值,并且一直赋值)
            return self.father[u]

    # 判断 u 和 v 是否找到同一个根
    def is_same(self,u, v): 
        u = self.find(u)
        v = self.find(v)
        return u == v

    # 将 v->u 这条边加入并查集 (并)
    def join(self,u, v):
        u = self.find(u)  # 寻找 u 的根
        v = self.find(v)  # 寻找 v 的根
        if u == v:
            return  # 如果发现根相同,说明在一个集合,不用两个节点相连,直接返回
        self.father[v] = u #否则其中一根归于另一根

心得

类比于树的递归序遍历和回溯算法,dfs如果是无返回值的,则其目的是遍历所属结构的过程中(树,图),将外部的visited(如参数)修改用来记录信息。

而带返回值的dfs,逻辑类似于树形DP,需考虑单层递归逻辑,需要从下层接受信息,并向上层返回信息。

posted @ 2024-09-09 00:18  haohaoscnblogs  阅读(14)  评论(0)    收藏  举报