并查集(并茶几)

并查集(并茶几)的应用

一、What‘s that?

并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。 ——百度百科

二、How to uphold

0.我们的需求

一开始我们拥有一个集族(集合的集合),集族中所有集合不相交,每个集合有且仅有一个元素。
在这里插入图片描述
百度百科告诉我们 :
并查集至少需要支持两种操作:

1.合并某两个集合a,b。

2.询问两个元素x,y是否属于同一集合。

1.可行的想法

我们能够想到用树的结构存储这样一个集合,每一次的合并就相当于将其中一棵树的根的父亲改为另一子树的根的编号。

记录一个值f[i]表示i的父亲结点(当i为根时,将父亲视为自己,也就是f[i]=i)。
所以我们有一个性质:若f[i]=i,则i为此树的根
初始时所有的点的f[i]=i。

2.初始化

void init(){ for (int i=1;i<=n;i++) f[i]=i; } 

3.查找树根

那么如何寻找一个结点所在的树的根呢?
Q:只要沿着f[i]一直向上跳到f[i]=i,此时的i结点便是树根

int find(int x){ return f[x]==x?x:find(f[x]);}    
//1.如果f[x]==x,返回树根为x; 2.如果f[x]!=x,继续搜索x的祖先f[x]是否为树根 

维护合并

接下来维护集合的合并操作。
例如我们需要合并2和6所在的集合:

那么只需要将2子树的根结点(1结点),与6子树的根结点(5结点)连接即可:

void Union(int x,int y)
{int xx=find(x),yy=find(y);                   //找到两树的根 if (xx!=yy) { f[yy]=xx; }                    //如果两树的根不同,合并两棵树 
}

5.优化原始并茶几

这就是并查集的“初始版本”了。
但我们发现它每一次的询问时间复杂度可能达到O(n)。
因为当数据退化为一条链的时候,每一次对链尾的find的次数是O(size)

a.按秩合并

所以这样一个数据结构在我们看来并不优,尝试着优化。
我们发现一棵较高的树x与一棵较低的树y合并时,应该从较高的树向较低的树连边,使其合并。是为按秩合并。(f[y]=x)

void Union(int x,int y)
{int xx=find(x),yy=find(y);                if (xx!=yy) { if (rank[xx]>rank[yy]) f[yy]=xx; //按秩合并,rank[xx]表示xx树的高度 else f[xx]=yy;                           }                   
}

这一优化据说能将时间复杂度降低为O(logn)

b.路径压缩

还有一个更简单且高效的优化:路径压缩
我们发现我们并不需要知道我们如何沿着f[i]向上跳,我们只需要找到最终的root就能够完成任务了。所以我们把f[i]的定义改为记录i的祖先结点的编号,在我们每一次find树根的时候,更新f[i]的值为树根,如此一来,当我们再次find(i)时,就能一步找到树根的位置。

int find(int x){ return f[x]=(f[x]==x?x:find(f[x])); }    
//将得到的值直接赋予f[x]即可 

这样路径压缩优化过后,每一次find时间复杂度理论上为O(1),union的时间也是O(1),这样我们就完成了一个能够维护部分集合操作的优秀数据结构。
一般情况下,路径压缩后的时间复杂度已经能达到O(1),所以通常不会写较为麻烦的按秩合并,so以下的所有代码舍弃按秩合并的优化。)

三、How to use it?

并查集是一个短小精悍的数据结构,因此在各类竞赛中的出现频率还是比较高的,并查集也有一些巧妙的思路与方法。

一般的并查集能够维护:

  1. 子树内信息。(通常在树根上维护)
  2. 从子结点到根的信息。

1.首先来一道裸题:亲戚

问题描述
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
规定
x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

数据输入:
第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。
数据输出:
P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系
样例:
input
6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6
output
Yes
Yes
No

solution:

一题并查集裸题,只是为了演示整体代码。明显只是维护集合的合并,询问结点连通性。

#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <cassert>
#include <string.h>
//#include <unordered_set>
//#include <unordered_map>#define MP(A,B) make_pair(A,B)
#define PB(A) push_back(A)
#define SIZE(A) ((int)A.size())
#define LEN(A) ((int)A.length())
#define FOR(i,a,b) for(int i=(a);i<(b);++i)
#define fi first
#define se secondusing namespace std;template<typename T>inline bool upmin(T &x,T y) { return y<x?x=y,1:0; }
template<typename T>inline bool upmax(T &x,T y) { return x<y?x=y,1:0; }typedef long long ll;
typedef unsigned long long ull;
typedef long double lod;
typedef pair<int,int> PR;
typedef vector<int> VI;const lod eps=1e-11;
const lod pi=acos(-1);
const int oo=1<<30;
const ll loo=1ll<<62;
const int mods=1e9+7;
const int INF=0x3f3f3f3f;//1061109567
const int MAXN=20005;
/*--------------------------------------------------------------------*/ 
//请省略以上部分int f[MAXN];
inline int read()
{int f=1,x=0; char c=getchar();while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+c-'0'; c=getchar(); }return x*f;
}
int find(int x){ return f[x]=(f[x]==x?x:find(f[x])); }
void Union(int x,int y)
{int xx=find(x),yy=find(y);                if (xx!=yy) f[xx]=yy;             
}
void init(int n){ for (int i=1;i<=n;i++) f[i]=i; } 
int main()
{int n=read(),m=read(),Case=read();init(n);for (int i=1;i<=m;i++){int x=read(),y=read();Union(x,y);}while (Case--){int x=read(),y=read();if (find(x)==find(y)) puts("Yes");else puts("No");}return 0;
}

2.一道稍稍不同的题:POJ1988 Cube Staking

Description
Farmer John and Betsy are playing a game with N (1 <= N <= 30,000)identical cubes labeled 1 through N. They start with N stacks, each containing a single cube. Farmer John asks Betsy to perform P (1<= P <= 100,000) operation. There are two types of operations:
moves and counts.

  • In a move operation, Farmer John asks Bessie to move the stack containing cube X on top of the stack containing cube Y.
  • In a count operation, Farmer John asks Bessie to count the number of cubes on the stack with cube X that are under the cube X and report that value.

Write a program that can verify the results of the game.

Input

  • Line 1: A single integer, P
  • Lines 2…P+1: Each of these lines describes a legal operation. Line 2 describes the first operation, etc. Each line begins with a ‘M’ for a move operation or a ‘C’ for a count operation. For move operations, the line also contains two integers: X and Y.For count operations, the line also contains a single integer: X.

Note that the value for N does not appear in the input file. No move operation will request a move a stack onto itself.

Output
Print the output from each of the count operations in the same order as the input file.

Sample Input
6
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4

Sample Output
1
0
2
Source
USACO 2004 U S Open

Solution

题意:
有两种操作:

  1. 将一堆立方体x堆在另一堆立方体y上面。
  2. 询问x下面有多少个立方体。

这一题需要维护两个额外的信息:

  1. r[i]表示从i结点到根节点一共有多少结点(i到根的距离+1)
  2. num[i]表示子树一共有多少结点。

只需要稍稍更改find的过程即可。

#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <cassert>
#include <string.h>
//#include <unordered_set>
//#include <unordered_map>#define MP(A,B) make_pair(A,B)
#define PB(A) push_back(A)
#define SIZE(A) ((int)A.size())
#define LEN(A) ((int)A.length())
#define FOR(i,a,b) for(int i=(a);i<(b);++i)
#define fi first
#define se secondusing namespace std;template<typename T>inline bool upmin(T &x,T y) { return y<x?x=y,1:0; }
template<typename T>inline bool upmax(T &x,T y) { return x<y?x=y,1:0; }typedef long long ll;
typedef unsigned long long ull;
typedef long double lod;
typedef pair<int,int> PR;
typedef vector<int> VI;const lod eps=1e-11;
const lod pi=acos(-1);
const int oo=1<<30;
const ll loo=1ll<<62;
const int mods=1e9+7;
const int INF=0x3f3f3f3f;//1061109567
const int MAXN=30005;
/*--------------------------------------------------------------------*/
//请省略以上部分int f[MAXN<<1],r[MAXN<<1],num[MAXN<<1];
inline int read()
{int f=1,x=0; char c=getchar();while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+c-'0'; c=getchar(); }return x*f;
}
inline char readc()
{char c=getchar();while (!isalnum(c)) c=getchar();return c;
}
inline int find(int x)
{if (f[x]==x) return x;int tmp=f[x];f[x]=find(f[x]);r[x]+=r[tmp];return f[x];
}
int main()
{int Case=read();for (int i=1;i<=MAXN;i++) f[i]=i,num[i]=1,r[i]=0;for (int i=1;i<=Case;i++){char c=readc();if (c=='M'){int x=read(),y=read();int xx=find(x),yy=find(y);if (xx!=yy){f[yy]=xx;r[yy]=num[xx];num[xx]+=num[yy];}}else {int x=read();int xx=find(x);printf("%d\n",num[xx]-r[x]-1);}} return 0;
}

本文总结

以上是一些并查集的简单应用,但也是一些精妙并查集技巧操作的基础。

若已经熟练掌握这些基础,那么可以学习一些其他更精妙并查集的方法:

  1. 并查集的结点删除
  2. 带权并查集(偏移向量、补集应用 Ps:名字很多
  3. 并查集的扩展域

这些内容以后博主会慢慢补充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/315910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从阿里中台战略看企业IT架构转型之道(上)

此文是我阅读《企业IT架构转型之道》一书的学习笔记的上半部分&#xff0c;所有内容出自钟华老师的这本书。零、为何阅读《企业IT架构转型之道》在加入X公司后&#xff0c;开始了微服务架构的实践&#xff0c;也开始了共享平台服务的建设&#xff0c;在这方面阿里巴巴的中台战略…

ML.NET机器学习、API容器化与Azure DevOps实践(四):持续集成与k8s持续部署

通过上文所介绍的内容&#xff0c;我们已经完成了RESTful API的开发&#xff0c;现在&#xff0c;就可以使用Azure DevOps来进行持续集成&#xff08;CI&#xff09;和k8s持续部署&#xff08;CD&#xff09;了。本文我会对使用Azure DevOps进行CI/CD的过程中需要注意的地方进行…

P3195 [HNOI2008]玩具装箱

P3195 [HNOI2008]玩具装箱 题意&#xff1a; n件玩具&#xff0c;第i件玩具经过压缩后的一维长度为CiC_iCi​,现在把玩具装入一维容器中&#xff0c;要求&#xff1a; 在一个一维容器中的玩具编号是连续的如果一个一维容器中有多个玩具&#xff0c;那么两件玩具之间要加入一…

卷积与莫比乌斯反演

卷积与莫比乌斯反演 目录 卷积与莫比乌斯反演 0前言 0.1前置技能 0.2问题的引入 1.简单定义 1.1数论函数的定义 1.2卷积的定义 1.3反演的基本形式 2.1莫比乌斯反演 3.1例题&#xff1a;【luogu-P2257 YY的GCD】 题目大意&#xff1a; solution1 solution2 0.前言 莫比…

ML.NET机器学习、API容器化与Azure DevOps实践(三):RESTful API

通过上文所述案例&#xff0c;我们已经选择了最优回归算法来预测学生的综合成绩&#xff0c;并且完成了基于训练数据集的预测模型训练。从实现上&#xff0c;训练好的模型被保存成一个ZIP文件&#xff0c;以便在其它项目中直接调用以完成机器学习的实践场景。在本文中&#xff…

杜教筛

杜教筛 1.概述 杜教筛是用以解决积性函数前缀和的算法。 在学习了莫比乌斯反演之后&#xff0c;杜教筛的过程就会显得简单而自然。 2.基本形式 对于积性函数&#xff0c;我们定义如下函数&#xff1a; 构造积性函数 &#xff0c;使得 显然 &#xff1a; 进一步转化&#xf…

ML.NET机器学习、API容器化与Azure DevOps实践(二):案例

在上文中&#xff0c;我简单地介绍了机器学习以及ML.NET的相关知识&#xff0c;从本讲开始&#xff0c;我会基于一个简单的案例&#xff1a;学生成绩预测&#xff0c;来介绍使用ML.NET进行机器学习以及API部署的基本过程。本案例的数据来源为加州大学尔湾分校的机器学习公开样本…

业界萌新对斯坦纳树的小结

业界萌新对斯坦纳树的小结 0.简介 斯坦纳树问题是组合优化问题&#xff0c;与最小生成树相似&#xff0c;是最短网络的一种。最小生成树是在给定的点集和边中寻求最短网络使所有点连通。而最小斯坦纳树允许在给定点外增加额外的点&#xff0c;使生成的最短网络开销最小。 ——…

cf1208E. Let Them Slide

cf1208E. Let Them Slide 题意&#xff1a; 都放在一个长度为W的框里面。有n个序到&#xff0c;第i个序列的长度是1。这些序到并排放在一起&#xff0c;每一个序列都放在一个长度为w的框里 这些序列可以在框里面滑动&#xff0c;但是不能划出框。 对于每一个位置&#xff0…

结合eShopOnWeb全面认识领域模型架构

一.项目分析在上篇中介绍了什么是"干净架构"&#xff0c;DDD符合了这种干净架构的特点&#xff0c;重点描述了DDD架构遵循的依赖倒置原则&#xff0c;使软件达到了低藕合。eShopOnWeb项目是学习DDD领域模型架构的一个很好案例&#xff0c;本篇继续分析该项目各层的职…

SOS_dp算法

Codeforces博客 简介&#xff1a; 前置知识&#xff1a;状压dp Sum over Subsets dynamic programming&#xff0c;简称Sos dp,状压dp的一种 用一个列题引出SOS dp&#xff1a; 给你一个由2N2^N2N个整数组成的确定数组A&#xff0c;我们需要计算对于任意的x&#xff0c;F(x)所…

微软开源Bing搜索背后的关键算法

微软今天宣布开源了一项 Bing 搜索背后的关键算法 —— SPTAG&#xff0c;它使 Bing 能够快速将搜索结果返回给用户。仅在几年前&#xff0c;网络搜索很简单&#xff0c;用户输入几个关键词然后浏览结果页面。现如今&#xff0c;这些用户可能会在手机上拍照并将其放入搜索框中&…

Stern-Brocot Tree

Stern-Brocot Tree 0.简介 Stern-Brocot Tree&#xff0c;俗称SB树&#xff08;滑稽&#xff09;。它能够表示出所有的最简分数&#xff0c;如下图。 1.一些规律 显然&#xff0c;对于两个相邻的最简分数 可以得到另一个最简分数 这样就可以在Stern-Brocot Tree上表示出所有…

FWT(快速沃尔什变换)

文章目录引入&#xff1a;or卷积and卷积xor卷积IFWT模板&#xff1a;例题&#xff1a;引入&#xff1a; FFT/NTT是用来解决∑ijkA[i]B[j]\sum_{ijk}A[i]B[j]∑ijk​A[i]B[j]的式子 而FWT是用来解决Ci∑j⊕kiAjBkC_i\sum_{j⊕ki}A_jB_kCi​∑j⊕ki​Aj​Bk​ ​ FWT是一种用于处…

教你自制.NET Core Global Tools

点击上方蓝字关注“汪宇杰博客”命令行是程序员装逼利器&#xff0c;.NET Core也可以写命令行程序&#xff0c;但是如何分发给其他程序员使用&#xff0c;一直是个问题。现在&#xff0c;有了.NET Core Global Tools&#xff0c;可以很方便的解决分发问题&#xff0c;我们来看看…

三点间LCA

三点间LCA 1.直接上题——jzoj5883. 【NOIP2018模拟A组9.25】到不了 Dscription wy 和 wjk 是好朋友。 今天他们在一起聊天&#xff0c;突然聊到了以前一起唱过的《到不了》。 “说到到不了&#xff0c;我给你讲一个故事吧。” “嗯&#xff1f;” “从前&#xff0c;神和凡人…

微软拥抱开源,Win10为啥要引入真Linux4.X内核?

来源 | 异步 | 文末赠书2019 年微软 Build 开发者大会在雷德蒙德召开。继将 Bash shell、原生 OpenSSH、WSL 引入 Windows&#xff0c;以及在微软商店提供 Ubuntu、SUSE Linux 和 Fedora 等发行版&#xff0c;微软又宣布了一个重大的决定 —— 将完整的 Linux 内核引入 Windows…

F.孤独(牛客小白月赛39)

F.孤独&#xff08;牛客小白月赛39&#xff09; 题意&#xff1a; 给定一棵树&#xff0c;寻找一个路径&#xff0c;将断掉所有与这个路径上的点相连的边&#xff0c;使得剩下的最大连通块的大小最小 题解&#xff1a; 这题有点印象&#xff0c;感觉做过&#xff0c;至少这…

分布式 - 分布式系统的特点

20世纪60年代&#xff0c;IBM研发了System 360架构大型机&#xff0c;与同时期的波音707、福特汽车誉为商业三大成就&#xff0c;凭借其卓越的性能和良好的稳定性&#xff0c;开启了大型机的时代&#xff0c;诞生了非常多的集中式系统&#xff0c;采用单机架构&#xff0c;有非…

[WC2011][BZOJ2115] Xor

BZOJ2115 Xor 题目描述&#xff1a; 题目大意&#xff1a; 给定一张 n 个点 m 条边的无向带权连通图&#xff0c;求一条从点 1 到点 n 的路径&#xff0c;使得经过的边权异或和最大。 路径可以经过重复点和重复边&#xff0c;当一条边被重复经过时也会相应地被 xor 多次。 s…