深度优先搜索与并查集

embedded/2024/10/23 14:30:05/

一:深度优先搜索

示例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-cif color[neighbor] == -1:if not dfs(neighbor, 1 - c):return False# 如果相邻节点已着色且颜色与当前节点相同,则图不是二分的elif color[neighbor] == c:return False# 如果所有相邻节点都满足条件,返回 Truereturn True# 遍历所有节点,确保每个连通分量都被检查for i in range(1, n + 1):if color[i] == -1:# 如果发现未着色的节点,从该节点开始 DFS 检查if not dfs(i, 0):return False# 如果所有节点都满足二分条件,返回 Truereturn 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 Listdef canBeSorted(nums: List[int]) -> bool:# 如果数组已经是有序的,直接返回Trueif 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  # 如果原始位置和排序后的位置不在同一个集合,返回Falsereturn True  # 所有元素都可以在同一个集合内排序,返回True

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

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

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

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

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

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

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

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


http://www.ppmy.cn/embedded/124503.html

相关文章

Maven - 依赖管理

依赖配置 在pom.xml的project标签内添加dependencies标签&#xff0c;之后添加依赖配置。 <dependencies><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.4.5</version>…

【VUE】Virtual Dom的优势在哪里

Virtual DOM 是一个轻量的 JavaScript 对象模型&#xff0c;它以 JS 对象的形式来描述真实的 DOM &#xff0c;可以在内存中进行操作、比较&#xff0c;然后只对需要更新的部分进行实际的 DOM 操作&#xff0c;从而最小化 DOM 操作的次数&#xff0c;提高渲染效率。 Vue.js 中…

胡超:引领中美能源与文化合作的创意先锋

中美能源合作领域迎来了一个重要的历史时刻,2024年中美可持续发展峰会(Sino-American Symposium on Sustainable Development)在全球关注下圆满落幕。这场峰会不仅成为了中美两国绿色能源合作的高端平台,也展示了作为该活动的协办方RES(Reverse Energy Solutions)在清洁能源领域…

【杂谈一之概率论】CDF、PDF、PMF和PPF概念解释与分析

一、概念解释 1、CDF&#xff1a;累积分布函数&#xff08;cumulative distribution function&#xff09;&#xff0c;又叫做分布函数&#xff0c;是概率密度函数的积分&#xff0c;能完整描述一个实随机变量X的概率分布 2、PDF&#xff1a;连续型概率密度函数&#xff08;p…

CPU 多级缓存

在多线程并发场景下&#xff0c;普通的累加很可能错的 CPU 多级缓存 Main Memory : 主存Cache : 高速缓存&#xff0c;数据的读取存储都经过此高速缓存CPU Core : CPU 核心Bus : 系统总线 CPU Core 和 Cache 通过快速通道连接&#xff0c;Main menory 和 Cache 都挂载到 Bus 上…

EtherNet/IP 转 EtherNet/IP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherNet/IP 转 EtherNet/IP GW系列型号 MS-GW22 概述 简介 MS-GW22 是 EtherNet/IP 和 EtherNet/IP 协议转换网关&#xff0c;…

QT 鼠标和键盘事件

在Qt中&#xff0c;可以使用事件处理机制来监听和处理鼠标事件和键盘事件。具体来说&#xff0c;重载事件处理函数或者使用事件过滤器是最常见的方法。以下是一些常用的事件处理函数以及如何监听鼠标事件和键盘事件的示例。 1. 处理鼠标事件 要处理鼠标事件&#xff0c;可以重…

如何查看NVIDIA Container Toolkit是否配置成功

要确认 NVIDIA Container Toolkit 是否已成功配置&#xff0c;可以按照以下步骤进行检查&#xff1a; 1.检查 NVIDIA 驱动程序 首先&#xff0c;确保你的系统已经正确安装了 NVIDIA 驱动程序&#xff0c;并且可以识别你的 GPU。你可以使用 nvidia-smi 命令来进行检查&#xf…