查并集

The White Belt

Good developers who are familiar with the entire stack know how to make life easier for those around them.

用于不相交集合的数据结构——查并集

不相交集合数据结构的概念和操作:

    不相交集合数据结构(disjoing-set data structure)保持一组不相交的动态集合S={S1,S2,S3,……Sk}。每个集合通过一个代表来识别,代表即集合中的某个成员。

不相交集合数据结构支持如下操作:

1 . MAKE_SET(x): 建立一个新的集合,其唯一成员就是x,所以其代表也就是自己。因为各集合是不相交的,故要求x没有在其他集合中出现过。

2 . UNION(x,y): 将包含x和y的动态集合(比如说Sx和Sy)合并为一个新的集合(即这两个集合的并集)。假定这个操作之前是不相交的。在经过此操作后,所得集合的代表可以是Sx U Sy中的任何一个成员,但在UNION的很多实现细节中,都选择Sx或Sy的代表作为新的代表。由于要求各集合是不相交的,故我们“消除”集合Sx和Sy,把它们从S中删去。

3 . FIND_SET(x):返回一个指针,指向包含x的(唯一)集合的代表。

不相交数据集合一般用链表或森林(有根树,如下图)来实现,有根树的速度更快。用有根树来表示集合,树中的每个节点都包含集合的一个成员,每棵树都表示一个集合。从下图中可知,不相交森林中,每个成员仅指向其父节点。每个树的根包含了代表,并且是它自己的父节点。尽管采用了这种表示的直观算法并不比采用链表表示的直观算法并不比采用链表表示的算法更快,但是,通过引入两种启发式策略(“按秩合并”和“路径压缩”),就可以获得目前已知的,渐进意义上最快的不相交集合的数据结构。

改进运行时间的启发式策略:

1.按秩合并,其思想是是用包含较少节点的树的根指向包含较多节点的树的根。我们并不显式地记录以每个节点为根的子树大小,而是采用了一种能够简化分析的方法。对每个节点,用秩表示节点高度的一个上界。在按秩合并中具有较小秩的根在UNION操作中要指向具有较大秩的根。

2.路径压缩,它非常简单而有效。如下图所示,在FIND_SET操作中,利用这种启发式策略,来使查找路径上的每个节点都直接指向根节点。路径压缩并不改变节点的秩。


不相交集合森林的伪代码:


带路径压缩的FIND_SET过程也是相当简单的:


过程FIND_SET是一种两趟方法,也就是说它干了两件事:

1.沿着路径上升,找到代表。

2.修改路径上的元素的p域,使其指向代表。

代码示例:

  1. public void make_set(int x) {/* 创建一个单元集 */  
  2.         pa[x] = x;  
  3.         rank[x] = 0;  
  4.     }  
  5.   
  6. public int find_set(int x) {/*带路径压缩的查找 */  
  7.         /* 保存待查找的数 */  
  8.         if(x != pa[x])   
  9.             pa[x] = find_set(pa[x]);  
  10.         return pa[x];  
  11.     }  
  12.   
  13.     /* 按秩合并x,y所在的集合 */  
  14.     public void union_set(int x, int y) {  
  15.         x = find_set(x);  
  16.         y = find_set(y);  
  17.         if (x == y)  
  18.             return;  
  19.         if (rank[x] > rank[y])/* 让rank比较高的作为父结点 */  
  20.         {  
  21.             pa[y] = x;  
  22.               
  23.         } else {  
  24.             pa[x] = y;  
  25.             if (rank[x] == rank[y])  
  26.                 rank[y]++;  
  27.               
  28.         }  
  29.     }  

不相交集合数据结构的一个应用:

    不相交集合数据结构有多种应用,其中之一是用于确定一个无向图中连通子图的个数。在下面给出的过程CONNECTED_COMPONENTS中,利用了不相交集合操作来计算一个图的连通子图。一旦CONNECTED_COMPONENTS作为预处理步骤执行后,过程SAME_COMPONENT回答两个顶点是否在同一连通子图的查询。


参考:http://blog.youkuaiyun.com/lalor/article/details/7388524、《算法导论》


版权声明:本文为博主原创文章,未经博主允许不得转载。

0
0
«上一篇: 一维和二维子数组之和最大值
»下一篇: Java中读取输入方式的性能比较
posted @ 2014-01-21 21:18 StackMer 阅读( 32) 评论( 0) 编辑 收藏

<think>嗯,用户现在问的是BFS、DFS和查并集(也就是并查集)。我需要先回顾一下这三个概念的定义、用途和区别。用户之前已经问过图论的应用,现在可能是在深入学习相关算法,或者是为面试或考试做准备。 首先,BFS(广度优先搜索)和DFS(深度优先搜索)是图遍历的基本算法,而并查集(Disjoint Set Union, DSU)用于处理集合的合并与查询问题。用户可能想了解这三者的基本概念、应用场景以及它们之间的不同。 接下来要考虑用户的需求层次。他们可能想知道每个算法的基本原理、代码实现、时间复杂度,以及实际应用例子。或者,他们可能需要比较这三者的优缺点,了解在什么情况下使用哪种算法更合适。 需要确保回答结构清晰,分点解释每个算法,然后比较它们。可能还需要举一些实际的例子,比如BFS用于最短路径,DFS用于拓扑排序,并查集用于连通性问题。同时,要注意用户之前的问题是关于图论的应用,所以这里需要保持连贯性,说明这些算法如何应用于实际问题。 另外,用户可能混淆了“查并集”和“并查集”,正确的术语是并查集,所以回答中需要明确指出,避免混淆。同时,要注意用中文术语,比如“节点”而不是“结点”,保持一致性。 要检查是否有常见的误区需要澄清,比如BFS和DFS的时间复杂度在不同数据结构(邻接矩阵 vs 邻接表)下的差异,或者并查集的路径压缩和按秩合并优化。 最后,总结部分需要简明扼要,强调三者的不同应用场景和选择依据,帮助用户根据具体问题选择合适的算法。</think>--- ### **BFS(广度优先搜索)** #### **核心思想** - **逐层扩散**:从起点出发,按层次遍历所有可达节点(先访问距离起点最近的节点)。 - **数据结构**:队列(FIFO)。 - **时间复杂度**:$O(V + E)$($V$为节点数,$E$为边数)。 #### **典型应用** 1. **无权图的最短路径**(如迷宫逃生、社交网络中的最少中间人); 2. **状态空间搜索**(如八数码问题、单词接龙); 3. **网络爬虫**(按层级抓取网页,避免重复访问)。 #### **代码模板(Python)** ```python from collections import deque def bfs(start, graph): visited = set() queue = deque([start]) visited.add(start) while queue: node = queue.popleft() for neighbor in graph[node]: if neighbor not in visited: visited.add(neighbor) queue.append(neighbor) ``` --- ### **DFS(深度优先搜索)** #### **核心思想** - **递归深入**:从起点出发,沿一条路径走到尽头,再回溯探索其他分支。 - **数据结构**:栈(LIFO)或递归调用栈。 - **时间复杂度**:$O(V + E)$。 #### **典型应用** 1. **拓扑排序**(如课程选修顺序、任务依赖关系); 2. **连通性问题**(如岛屿数量、图的连通分量); 3. **回溯法**(如数独求解、全排列生成)。 #### **代码模板(Python)** ```python def dfs(node, graph, visited): if node in visited: return visited.add(node) for neighbor in graph[node]: dfs(neighbor, graph, visited) ``` --- ### **并查集(Disjoint Set Union, DSU)** #### **核心思想** - **动态连通性管理**:通过树结构合并集合,并快速判断两个元素是否属于同一集合。 - **优化手段**:路径压缩(降低树高) + 按秩合并(平衡树结构)。 - **时间复杂度**:接近$O(1)$(经过优化后)。 #### **典型应用** 1. **动态连通性问题**(如社交网络好友关系合并、电路连通性检测); 2. **最小生成树(Kruskal算法)**:判断边是否会形成环; 3. **图像像素连通区域标记**。 #### **代码模板(Python)** ```python class DSU: def __init__(self, size): self.parent = list(range(size)) self.rank = [1] * size def find(self, x): if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) # 路径压缩 return self.parent[x] def union(self, x, y): x_root = self.find(x) y_root = self.find(y) if x_root == y_root: return # 按秩合并 if self.rank[x_root] < self.rank[y_root]: self.parent[x_root] = y_root else: self.parent[y_root] = x_root if self.rank[x_root] == self.rank[y_root]: self.rank[x_root] += 1 ``` --- ### **三者对比与选择原则** | **算法** | **核心目标** | **适用场景** | **优势** | **局限性** | |----------|---------------------------|----------------------------------|----------------------------|-------------------------| | BFS | 最短路径(无权图) | 层级遍历、状态扩散 | 保证最优解 | 空间复杂度高(存储队列) | | DFS | 路径存在性、连通性 | 回溯问题、拓扑排序 | 空间复杂度低(仅递归栈) | 可能陷入深层无效路径 | | 并查集 | 动态集合合并与查询 | 连通性判断、集合动态合并 | 近乎常数时间操作 | 无法回溯合并操作 | --- ### **实际场景示例** 1. **社交网络好友推荐** - **BFS**:计算用户A的N度人脉(二度好友优先推荐); - **并查集**:判断用户A和用户B是否属于同一社群(如“校友群”)。 2. **游戏地图生成** - **DFS**:生成随机迷宫(确保起点到终点有唯一通路); - **BFS**:AI自动寻路(寻找最短逃生路径)。 3. **电路设计验证** - **并查集**:检查所有元件是否连通(避免断路)。 --- ### **总结** - **BFS**:**最短路径**问题优先选择,需注意空间消耗; - **DFS**:适合**探索所有可能性**或**连通性检测**,注意递归深度限制; - **并查集**:**动态集合操作**的终极工具,高效解决连通性问题。 **选择策略**: - 需要最短路径 → BFS - 需要遍历所有可能或递归结构 → DFS - 动态合并与查询 → 并查集
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值