首页 > 其他分享 >深度优先搜索与并查集

深度优先搜索与并查集

时间:2024-10-08 12:53:17浏览次数:3  
标签:优先 查集 find 搜索 数组 集合 True 节点

一:深度优先搜索

示例1

题目链接:886. 可能的二分法 - 力扣(LeetCode)

首先可以构建一个图的邻接表表示,然后使用深度优先搜索(DFS)算法来检查图是否可以二分。如果图可以二分,则返回 True;否则返回 False。具体步骤如下:

  1. 构建图:使用一个列表 graph 来存储每个节点的邻接节点。
  2. 初始化颜色数组:使用一个数组 color 来记录每个节点的颜色,-1 表示未着色。
  3. DFS 检查:定义一个 DFS 函数,递归地为每个节点着色,并检查其相邻节点的颜色是否满足二分条件。
  4. 遍历所有节点:确保每个节点都被检查,如果发现未着色的节点,则从该节点开始进行 DFS 检查。
  5. 返回结果:如果所有节点都满足二分条件,则返回 True;否则返回 False。
def possibleBipartition(n, dislikes):
    # 创建一个大小为 n+1 的列表,用于存储图中的每个节点及其相邻节点
    graph = [[] for _ in range(n + 1)]
    
    # 遍历 dislikes 列表,构建图的邻接表表示
    for u, v in dislikes:
        graph[u].append(v)  # 添加 v 到 u 的邻接列表
        graph[v].append(u)  # 添加 u 到 v 的邻接列表

    # 初始化颜色数组,-1 表示节点尚未着色
    color = [-1] * (n + 1) 

    # 定义深度优先搜索(DFS)函数,用于检查图是否可以二分
    def dfs(node, c):
        # 将当前节点着色为 c(0 或 1)
        color[node] = c
     
        # 遍历当前节点的所有相邻节点
        for neighbor in graph[node]:
            # 如果相邻节点未着色,则递归调用 dfs 着色为 1-c
            if color[neighbor] == -1:
                if not dfs(neighbor, 1 - c):
                    return False
            # 如果相邻节点已着色且颜色与当前节点相同,则图不是二分的
            elif color[neighbor] == c:
                return False
        # 如果所有相邻节点都满足条件,返回 True
        return True

    # 遍历所有节点,确保每个连通分量都被检查
    for i in range(1, n + 1):
        if color[i] == -1:
            # 如果发现未着色的节点,从该节点开始 DFS 检查
            if not dfs(i, 0):
                return False
    # 如果所有节点都满足二分条件,返回 True
    return True

 这段代码实现了 possibleBipartition 函数,用于判断是否可以将一组人分成两个组,使得每个人都不与其不喜欢的人在同一组。

示例2

题目链接:547. 省份数量 - 力扣(LeetCode)

总体思路如下:

  1. 初始化一个布尔数组 visited 来跟踪每个城市是否已经被访问过。
  2. 初始化一个计数器 count 来记录省份的数量。
  3. 使用深度优先搜索(DFS)来遍历图中的每个城市,找到所有相连的城市。
  4. 对于每个未访问的城市,执行DFS以找到与它相连的所有城市,并将它们标记为已访问。
  5. 每当找到一个未访问的城市时,就意味着发现了一个新的省份,因此将 count 加一。
  6. 最终返回 count 的值,即省份的数量。
def findCircleNum(isConnected):
    n = len(isConnected)  # 获取城市的数量
    visited = [False] * n  # 初始化访问数组,所有城市初始状态都是未访问
    count = 0  # 初始化省份计数器

    def dfs(node):  # 定义深度优先搜索函数
        visited[node] = True  # 标记当前城市为已访问
        for neighbor in range(n):  # 遍历所有城市
            if isConnected[node][neighbor] == 1 and not visited[neighbor]:  # 如果当前城市与邻居城市相连且邻居未访问
                dfs(neighbor)  # 递归地访问邻居城市

    for i in range(n):  # 遍历所有城市
        if not visited[i]:  # 如果当前城市未访问
            dfs(i)  # 从当前城市开始进行深度优先搜索
            count += 1  # 发现一个新的省份,计数器加一

    return count  # 返回省份的总数

这段代码通过深度优先搜索(DFS)来找到并计数图中的连通分量,每个连通分量代表一个省份。visited 数组用于避免重复访问城市,从而确保每个省份只被计数一次。最终,count 变量包含了省份的总数,并被返回作为结果。这个方法的时间复杂度是 O(n^2),因为它需要遍历整个邻接矩阵,并且对于每个城市,可能需要遍历所有其他城市。空间复杂度是 O(n),用于存储 visited 数组和递归调用栈。

二:并查集

并查集可以结合其他参考材料的示意图进行理解。

示例1

题目链接:1971. 寻找图中是否存在路径 - 力扣(LeetCode)

总体思路如下:

  1. 使用并查集(Union-Find)数据结构来处理图的连通性问题。
  2. 初始化并查集,使得每个顶点初始时是其自身的父节点。
  3. 通过遍历所有边,将每对连接的顶点合并到同一个集合中。
  4. 最后,通过查找source和destination的根节点来判断它们是否在同一个集合内,从而确定是否存在从source到destination的有效路径。
def validPath(n, edges, source, destination):
    # 初始化并查集,每个顶点初始时是其自身的父节点
    parent = list(range(n))

    # 查找根节点,使用路径压缩优化查找效率
    def find(x):
        # 如果当前节点的父节点不是它自己,则递归地找到其根节点
        if parent[x] != x:
            parent[x] = find(parent[x])  # 路径压缩,将查找路径上的所有节点直接连接到根节点
        return parent[x]

    # 合并两个集合,将两个顶点所在的集合合并
    def union(x, y):
        # 找到两个顶点的根节点
        rootX = find(x)
        rootY = find(y)
        # 如果两个顶点不在同一个集合中,则合并它们
        if rootX != rootY:
            parent[rootY] = rootX  # 将一个根节点作为另一个根节点的父节点

    # 构建并查集,遍历所有边,合并每个边连接的两个顶点
    for u, v in edges:
        union(u, v)

    # 检查source和destination是否在同一个集合中,如果在,则存在有效路径
    return find(source) == find(destination)

并查集是一种树型的数据结构,用于处理一些不交集的合并及查询问题。初始化时,每个顶点都是一个独立的集合。find函数用于查找顶点的根节点,并进行路径压缩,以优化后续的查找操作。union函数用于合并两个顶点所在的集合,通过连接它们的根节点来实现。遍历所有边,将连接的顶点合并到同一个集合中,这样所有直接或间接连接的顶点最终都会在同一个集合内。最后,通过比较source和destination的根节点是否相同,可以判断它们是否连通,从而确定是否存在有效路径。

示例2

题目链接:1998. 数组的最大公因数排序 - 力扣(LeetCode)

总体思路如下:

  1. 首先,检查数组是否已经是有序的,如果是,直接返回True。
  2. 使用并查集(Union-Find)数据结构来管理和查找元素之间的连接关系。
  3. 对于数组中的每一对元素,如果它们的最大公因数(gcd)大于1,则将它们在并查集中合并,表示它们可以交换位置。
  4. 最后,检查通过并查集合并后的集合是否能够与排序后的数组匹配,如果可以,返回True,否则返回False。
from math import gcd
from typing import List

def canBeSorted(nums: List[int]) -> bool:
    # 如果数组已经是有序的,直接返回True
    if nums == sorted(nums):
        return True

    # 初始化并查集,每个元素的初始父节点是它自己
    parent = list(range(len(nums)))

    # 查找根节点,并进行路径压缩优化
    def find(x):
        if parent[x] != x:
            parent[x] = find(parent[x])  # 路径压缩
        return parent[x]

    # 合并两个集合
    def union(x, y):
        rootX = find(x)
        rootY = find(y)
        if rootX != rootY:
            parent[rootY] = rootX  # 将y的根节点指向x的根节点

    # 构建并查集,将可以交换的元素放在同一个集合中
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if gcd(nums[i], nums[j]) > 1:
                union(i, j)  # 如果gcd大于1,合并i和j的集合

    # 检查每个集合中的元素是否可以排序
    sorted_nums = sorted(nums)
    for i in range(len(nums)):
        if find(i) != find(nums.index(sorted_nums[i])):
            return False  # 如果原始位置和排序后的位置不在同一个集合,返回False

    return True  # 所有元素都可以在同一个集合内排序,返回True

该代码利用并查集来处理数组元素之间的交换关系,通过检查元素是否可以交换位置来决定数组是否可以通过交换达到有序状态。并查集通过路径压缩和按秩合并优化,使得查找和合并操作更加高效。代码首先检查数组是否已经有序,然后通过遍历数组中的元素对,将可以交换的元素合并到同一个集合中。最后,通过比较原始数组和排序后数组的元素是否在同一个集合中,来判断是否可以通过交换达到有序状态。如果所有元素都可以在同一个集合内排序,则返回True,否则返回False。

这种方法之所以有效,是因为它基于以下两个关键概念:

  1. 最大公因数(GCD)与交换关系

    • 如果两个整数的最大公因数(GCD)大于1,这意味着它们有公共的质因数,可以认为它们在某种意义上是“相关联”的。在这个问题中,如果两个数有公共的质因数,它们可以通过一系列的交换操作相互到达对方的位置。
    • 因此,可以将所有互相关联的数视为一个“连通块”,在这个连通块内的任何数都可以通过交换到达任何其他位置。
  2. 并查集(Union-Find)结构

    • 并查集是一种数据结构,用于处理一些不交集的合并及查询问题。它支持两种操作:查找(Find)和合并(Union)。
    • 在这个问题中,并查集用于跟踪哪些数属于同一个连通块。如果两个数可以交换(即它们的GCD大于1),则将它们所在的集合合并。
    • 通过这种方式,最终每个集合中的数都可以互相交换,而不与其他集合中的数交换。

以下是为什么这种方法有效的原因:

  • 连通性保证:如果数组可以通过交换达到非递减顺序,那么每个数必须至少与一个其他数在同一个连通块中,这样它们才能通过一系列交换到达正确的位置。
  • 集合匹配:通过并查集,我们可以确保每个数与其排序后位置上的数属于同一个集合。如果这一点对所有数都成立,那么通过集合内数的相互交换,我们可以将数组排序。
  • 无序性检测:如果在任何位置上,原始数组的数与其排序后位置的数不属于同一个集合,那么就不可能通过交换将数组排序到非递减顺序。

综上所述,这种方法之所以有效,是因为它利用了数之间的关联性(通过GCD)和并查集来管理和验证这种关联性,从而确定数组是否可以通过交换达到有序状态。

标签:优先,查集,find,搜索,数组,集合,True,节点
From: https://blog.csdn.net/2301_80651329/article/details/142735637

相关文章

  • 优先级队列 ( PriorityQueue )
    文章目录前言一、优先级队列1.1、概念二、优先级队列的模拟实现2.1、堆的概念2.2、堆的存储方式 2.3、堆的创建2.4、堆的插入与删除三、常用接口介绍3.1、Top-k问题3.2、使用PriorityQueue创建大小堆,解决TOPK问题前言        前几篇我们讲解过队列,其是一种先......
  • 【算法】记忆化搜索
    【ps】本篇有5 道 leetcode OJ。 目录一、算法简介二、相关例题1)斐波那契数.1-题目解析.2-代码编写2)不同路径.1-题目解析.2-代码编写3)最长递增子序列.1-题目解析.2-代码编写4)猜数字大小II.1-题目解析.2-代码编写5)矩阵中的最长递增路径.1-题......
  • SpringBoot 多元化配置(正则表达式,配置文件优先级)
    1.配置绑定所谓“配置绑定”就是把配置文件中的值与JavaBean中对应的属性进行绑定。通常,我们会把一些配置信息(例如,数据库配置)放在配置文件中,然后通过Java代码去读取该配置文件,并且把配置文件中指定的配置封装到JavaBean(实体类)中。SpringBoot提供了以下2种方式进行配......
  • 搜索算法合集 - By DijkstraPhoenix
    搜索算法合集ByDijkstraPhoenix深度优先搜索(DFS)引入如果现在有一个迷宫,如何走路径最短?方法走迷宫最简单粗暴的方法式什么呢?当然是把所有路都走一遍啦!如果是手动计算的话,可能会把你手指累得抽筋,但电脑不会,电脑具有强大的算力,这种暴力的事情当然是交给电脑做啦。深......
  • zoomeye类似搜索引擎和子域名搜索
    与zoomeye类似的搜索引擎[fofa][网络空间测绘,网络空间安全搜索引擎,网络空间搜索引擎,安全态势感知-FOFA网络空间测绘系统](https://fofa.info/)[Shodan][https://www.shodan.io](https://www.shodan.io/)[Censys][https://search.censys.io](https://search.censys.io/)子......
  • 帝国cms搜索页模板关键字结果标题加红的方法
    要在帝国CMS的搜索结果页面上实现关键词高亮显示的功能,可以按照以下步骤操作:备份原有模板文件:在修改任何模板文件之前,请确保备份原有的模板文件,以防修改出错时能够恢复。定位到模板编辑器:登录帝国CMS后台。导航至“模板”->“模板列表”。找到需要修改的搜索列表模板文......
  • pbootcms如何显示按文章内容搜索,而不是搜索标题
    在PbootCMS中,默认情况下搜索功能通常是基于文章标题进行的。如果你想让搜索功能基于文章内容进行,可以通过以下步骤实现:步骤1:修改搜索表单在搜索表单中添加一个隐藏字段 field,并将它的值设为 content。这样可以让系统知道搜索时应该针对文章内容进行匹配。修改后的搜索表......
  • Codeforces2020D Connect the Dots(观察 + 并查集 + 差分)
    题意多组数据。数轴上有\(n\)个点,编号为\(1\simn\),对这些点做$m$次操作。每次操作给出三个整数\(a_i(1\lea_i\len)\\\d_i(1\led_i\le10)\\\k_i(0\lek_i\len)\)。将点\(a_i,a_i+d_i,a_i+2\timesd_i,a_i+3\timesd_i,\cdot\cdot\cdo......
  • <动态规划>Leetcode96.不同的二叉搜索树
    给你一个整数n,求恰由n个节点组成且节点值从1到n互不相同的二叉搜索树有多少种?返回满足题意的二叉搜索树的种数。解题步骤如下:二叉搜素树的相关概念寻找规律思路建立代码实现1.二叉搜索树的相关概念①:若左子树不空,则左子树所有节点的值均小于它的根节点的值。......
  • 信息学奥赛复赛复习10-CSP-J2020-03表达式求值-栈、后缀表达式、isdigit函数、c_str函
    PDF文档公众号回复关键字:202410031P7073[CSP-J2020]表达式[题目描述]小C热衷于学习数理逻辑。有一天,他发现了一种特别的逻辑表达式。在这种逻辑表达式中,所有操作数都是变量,且它们的取值只能为0或1,运算从左往右进行。如果表达式中有括号,则先计算括号内的子表达式的......