介绍

并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的 合并及查询 问题。
其主要操作为:
Union(合并) :将两个节点所在集合合并为一个集合
Find (查询) :查询某个节点属于哪个集合(即返回所在树的根节点)

图示:
通过上面的表述,恐怕我们并不清楚并查集到底是什么样子的?
下面我们用 图来表示一下

有四个节点我们编号为1,2,3,4,也可以有四棵树,每棵树只有一个节点,该节点即为根节点。这四棵树组成的集合(森林 就是我们所说的并查集

我们使用数组存储 ,上方所示即为

每个节点即为一个集合,每个集合的根节点为自己

合并: 所谓合并就是两棵树合并为一棵树,就是把上面两个节点联系在一起;比如我们合并1,2 两个节点,并且让2 为根节点(也可以1为根节点,这里以2为根节点示例)

经过合并之后,只有三个集合即三棵树 ,存储的数组变化为

编号为1节点和编号为2的节点 为一个集合,且以2为根,故1号位置存储编号2

查询: 找到系欸但所在集合的根节点
以查询编号为1的节点为例:
我们在数组中找1号位置值为2 ,与编号不同
查找2号位置,值为2,与编号相同,所以编号为1的节点的根节点为2

代码实现

经过了上面的演示,相信大家对于并查集有了最基本的认识,下面我们进行代码实现。

并查集的基本实现

const int N ; //并查集中的节点个数int p[N];  //存放节点所在树的根节点void init(){//初始化,刚开始每个节点都是一棵树,根节点就是它自己for(int i=1;i<=N;i++){p[i]=i;}
}int Find(int x){if(p[x]!=x){//如果不相等,则根节点并非x , 那么去找p[x]的父节点return find(p[x]);}return p[x];
}
void Union(int x,int y){// 找到x,y所在树的根节点int px=find(x),py=find(y);
//根节点不相同,则不是一棵树,进行合并if(px!=py){//合并后的根节点为pyp[px]=py;}
}

代码优化

路径压缩

在并查集的操作当中,我们最常使用的是 Find 查询操作 。查询操作的复杂度决定了整体运行效率。

那么思考一下什么情况下,查询操作效率会很低呢?
思考一下树的高度很大的时候。

倘若我们查询4号节点,需要从4号节点往上进行查询,复杂度即为O(h),h为树的高度 ,倘若对于深处节点查询频繁的话,效率会很低。

因此,为了避免这种情况,出现了路径压缩的方式,其思想是将上面图片的树,改造为如下所示:

每个子节点的父节点即为根节点,查询操作复杂度O(1)即可

优化代码:

int Find(int x){if(p[x]!=x){//如果不相等,则向上查找;find函数返回的结果一定为根节点// 查询结果赋值给p[x],设置当前节点x的父节点为根节点p[x]=find(p[x]);}return p[x];
}

按秩合并

关于 秩 其实有两种解释 :一种是树的高度,另一种是树的节点个数(集合大小)我们这里采用的是 树 的高度。

按秩合并其实是一种启发式合并 即:把小的数据结构 合并到大的数据结构当中,并且只增加小的数据结构的 查询 代价

情况如图所示: 两个集合进行合并

我们可以观察到最右边的图,合并后,进行查询,效率会更好,这种合并方式就是我们正在讨论的 按秩合并 。

优化代码

const int N ; //并查集中的节点个数int p[N];  //存放节点所在树的根节点
int rank[N] ;//存放秩的数组,每个集合的秩进存放在根节点
void init(){//初始化,刚开始每个节点都是一棵树,根节点就是它自己//每个节点的秩为1for(int i=1;i<=N;i++){p[i]=i;rank[i]=1; }
}int Find(int x){if(p[x]!=x){//路径压缩 优化p[x]= find(p[x]);}return p[x];
}
void Union(int x,int y){// 找到x,y所在树的根节点int px=find(x),py=find(y);//根节点不相同,则不是一棵树,进行合并if(px!=py){// 按秩合并 优化if(rank[px]<=rank[py]){p[px]=py;//两个集合的秩相同,合并后的集合在原基础上加1//原因如下图所示rank[py]+=(rank[x]==rank[y]?1:0);}else{p[py]=px;}}
}

题目练习:

1250. 格子游戏

链接: https://www.acwing.com/problem/content/1252/

Alice和Bob玩了一个古老的游戏:首先画一个 n×n 的点阵(下图 n=3 )。
接着,他们两个轮流在相邻的点之间画上红边和蓝边:
直到围成一个封闭的圈(面积不必为 1)为止,“封圈”的那个人就是赢家。因为棋盘实在是太大了,他们的游戏实在是太长了!
他们甚至在游戏中都不知道谁赢得了游戏。
于是请你写一个程序,帮助他们计算他们是否结束了游戏?
输入格式
输入数据第一行为两个整数 n 和 m。n表示点阵的大小,m 表示一共画了 m 条线。
以后 m 行,每行首先有两个数字 (x,y),代表了画线的起点坐标,接着用空格隔开一个字符,假如字符是 D,则是向下连一条边,如果是 R 就是向右连一条边。

输入数据不会有重复的边且保证正确。
输出格式
输出一行:在第几步的时候结束。
假如 m 步之后也没有结束,则输出一行“draw”。
数据范围
1≤n≤200 ,
1≤m≤24000
输入样例:
3 5
1 1 D
1 1 R
1 2 D
2 1 R
2 2 D
输出样例:
4

思路:
输入样例当中给了我们一个点,及其所画的方向 。其实根据这些信息,我们可以得到两个点的信息,那怎么判断是否形成封闭的圆呢?

如图所示,我们只需要画一条线,线的两端为(2,1) (2,2) 即可以形成封闭的圆 。这种情况我们使用并查集是最为简单的,判断两个点是否在同一集合内,在的话可以形成封闭的圆。

代码:

#include<iostream>
#include<cstring>using namespace std;const int N=40000+10;int p[N];
// 将二维坐标转换为唯一的数字表示
int get(int x,int y,int n){x--,y--;return x*n+y;
}int find(int x){if(p[x]!=x)  p[x]=find(p[x]);return p[x];
}int main(){int n=0,m=0;cin>>n>>m;//初始化for(int i=0;i<n*n;i++){p[i]=i;}for(int i=1;i<=m;i++){int x=0,y=0;char ch;cin>>x>>y>>ch;int a=get(x,y,n),b=0;if(ch=='D'){b=get(x+1,y,n);}else if(ch=='R'){b=get(x,y+1,n);}int pa=find(a),pb=find(b);if(pa==pb){//如果在同意集合内,形成了封闭的圆,直接返回结果printf("%d\n",i);return 0;}p[pa]=pb;}printf("draw\n");return 0;
}

1252. 搭配购买

链接:https://www.acwing.com/problem/content/1254/

Joe觉得云朵很美,决定去山上的商店买一些云朵。
商店里有 n 朵云,云朵被编号为 1,2,…,n,并且每朵云都有一个价值。
但是商店老板跟他说,一些云朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。
但是Joe的钱有限,所以他希望买的价值越多越好。
输入格式
第 1 行包含三个整数 n,m,w,表示有 n 朵云,m 个搭配,Joe有 w 的钱。
第 2∼n+1行,每行两个整数 ci,di 表示 i 朵云的价钱和价值。
第 n+2∼n+1+m 行,每行两个整数 ui,vi,表示买 ui 就必须买 vi,同理,如果买 vi 就必须买 ui。
输出格式
一行,表示可以获得的最大价值。
数据范围
1≤n≤10000,
0≤m≤5000,
1≤w≤10000,
1≤ci≤5000,
1≤di≤100,
1≤ui,vi≤n
输入样例:
5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2
输出样例:
1

思路:
题目中给我们提供了一些物品的价格和价值;要求买u必须要买v,买v也必须买u ; 倘若买v还必须买w ,则u,v,w 可以认为是一个整体,要买一起买,不买则全部不买;如果把所有关联的物品全部看作一个整体,对于每个整体而言,只有买或者不买两种选择,求一定价钱内能买到物品的最大价值 即可转换为经典的01背包问题 。

而那些物品可以看作一个整体,则可使用并查集进行求取,根节点存储整个集合的价格和价值。

代码:

#include<iostream>
#include<cstring>using namespace std;const int N=10000+10;int p[N];int cost[N],value[N];int find(int x){if(p[x]!=x) {p[x]=find(p[x]);}return p[x];
}int main(){int n=0,m=0,w=0;scanf("%d%d%d",&n,&m,&w);for(int i=0;i<n;i++){p[i]=i;}for(int i=0;i<n;i++){int c=0,d=0;scanf("%d%d",&c,&d);cost[i]=c,value[i]=d;}for(int i=0;i<m;i++){int u=0,v=0;scanf("%d%d",&u,&v);u--,v--;int pu=find(u),pv=find(v);if(pu!=pv){p[pu]=pv;//根节点加上新加入节点对应的价格和价值cost[pv]+=cost[pu];value[pv]+=value[pu];}}int dp[N];memset(dp,0,sizeof dp);for(int i=0;i<n;i++){if(p[i]==i){for(int j=w;j>=cost[i];j--){dp[j]=max(dp[j],dp[j-cost[i]]+value[i]);}}}cout<<dp[w]<<endl;return 0;
}

237. 程序自动分析

链接:https://www.acwing.com/problem/content/239/

在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设 x1,x2,x3,… 代表程序中出现的变量,给定 n 个形如 xi=xj 或 xi≠xj 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。
例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x1≠x4,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
输入格式
输入文件的第 1 行包含 1 个正整数 t,表示需要判定的问题个数,注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第 1 行包含 1 个正整数 n,表示该问题中需要被满足的约束条件个数。
接下来 n 行,每行包括 3 个整数 i,j,e,描述 1 个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 e=1,则该约束条件为 xi=xj;若 e=0,则该约束条件为 xi≠xj。
输出格式
输出文件包括 t 行。
输出文件的第 k 行输出一个字符串 YES 或者 NO,YES 表示输入中的第 k 个问题判定为可以被满足,NO 表示不可被满足。
数据范围
1≤n≤10^5
1≤i,j≤10^9
输入样例:
2
2
1 2 1
1 2 0
2
1 2 1
2 1 1
输出样例:
NO
YES

思路:
在这个题目当中,可以把输入分为两类,一类是相等的条件,另一类是不等的条件;倘若我们把相等的条件放在一遍,去看不等的条件,倘若不等的条件种两个变量出现在相等条件中,一定不被满足;倘若查询完毕后,没有出现刚才不满足情况,则一定是满足的。

把相等条件的变量全部放入并查集,查询不等的条件,倘若对应的两个变量在一个集合内部,则必不满足;不出现刚才必不满足的情况,则一定满足。

注: 数据量最大为 10^9 以此 来申请并查集的空间,则会超过题目给予的64MB大小;反观条件次数n为 10^6,则变量最多只有2 * 10^6 ,我们可以离散化处理,将大范围的变量处理到小范围上。

代码:

#include<iostream>
#include<cstring>
#include<vector>
#include<unordered_map>
using namespace std;/*
注:数据量过大,需要进行离散化,将数据放在较小的定义域内*/
const int N=2000010;
int p[N];
unordered_map<int,int> map;int find(int x){if(p[x]!=x){p[x]=find(p[x]);}return p[x];
}
//离散化处理函数
int get(int x){static int t=1;if(map.count(x)==0){map[x]=t++;}return map[x];
}int main(){int t=0;scanf("%d",&t);while(t--){for(int k=1;k<N;k++){p[k]=k;}int n=0;scanf("%d",&n);vector<pair<int,int>>  vec1;vector<pair<int,int>>  vec2;for(int k=0;k<n;k++){int i=0,j=0,e=0;scanf("%d%d%d",&i,&j,&e);if(e==0){vec1.push_back({get(i),get(j)});}else if(e==1){vec2.push_back({get(i),get(j)});}}//处理相等的条件for(int k=0;k<vec2.size();k++){int i=vec2[k].first,j=vec2[k].second;int pi=find(i),pj=find(j);if(pi!=pj){p[pi]=pj;}}//遍历不等的条件,继续判断int flag=true;for(int k=0;k<vec1.size();k++){int i=vec1[k].first,j=vec1[k].second;int pi=find(i),pj=find(j);if(pi==pj){flag=false;break;}}if(flag){puts("YES");}else{puts("NO");}}return 0;
}

238. 银河英雄传说

链接: https://www.acwing.com/problem/content/240/

有一个划分为 N 列的星际战场,各列依次编号为 1,2,…,N。
有 N 艘战舰,也依次编号为 1,2,…,N,其中第 i 号战舰处于第 i 列。
有 T 条指令,每条指令格式为以下两种之一:
M i j,表示让第 i 号战舰所在列的全部战舰保持原有顺序,接在第 j 号战舰所在列的尾部。
C i j,表示询问第 i 号战舰与第 j 号战舰当前是否处于同一列中,如果在同一列中,它们之间间隔了多少艘战舰。
现在需要你编写一个程序,处理一系列的指令。
输入格式
第一行包含整数 T,表示共有 T 条指令。
接下来 T 行,每行一个指令,指令有两种形式:M i j 或 C i j。
其中 M 和 C 为大写字母表示指令类型,i 和 j 为整数,表示指令涉及的战舰编号。
输出格式
你的程序应当依次对输入的每一条指令进行分析和处理:
如果是 M i j 形式,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息;
如果是 C i j 形式,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 i 号战舰与第 j 号战舰之间布置的战舰数目,如果第 i 号战舰与第 j 号战舰当前不在同一列上,则输出 −1。
数据范围
N≤30000,T≤500000
输入样例:
4
M 2 3
C 1 2
M 2 4
C 4 2
输出样例:
-1
1

思路:
对于判断是否是同一列,我们可以使用并查集,通过判断是否属于同一个结合来进行判断。

如果属于同一列(即同一集合),还需要输出之间的距离是多少,这个需要怎么处理呢?

倘若我们有一个数组d[ ] 记录并查集中每个节点到它所属集合的根节点的距离。根节点,我们设置为每列的第一个战舰。

则 对于两艘战舰x,y (x在y前面)而言,他们的距离为 d[y]-d[x]-1 则可以直接得到结果。

因此,我们只需要维护一个数组d[ ] ;

而维护d[ ] 主要在集合合并的时候,进行更新,那怎么更新呢?

对于py集合而言,每个节点到根节点的距离只需要加上 3 的长度即可
而3的长度 ,如图所示为2 + 1的结果,线段2的长度为1 ,而线段1的长度为整个px集合节点个数

因此,还需要维护一个数组 Size [ ] 存放每个集合的节点数目

代码实现:

#include<iostream>
#include<cstring>using namespace std;const int N=30000+10;int p[N];
//  Size 为该集合的元素个数  dist,该节点到父结点的距离
int Size[N],dist[N];int find(int x){if(p[x]!=x){int root=find(p[x]);//dist[x] 保存的是该节点到父节点的距离//dist[p[x]] 保存的是 x父节点到根节点的距离 (由上面的递归得到的)dist[x]+=dist[p[x]];p[x]=root;}return p[x];
}int main(){for(int i=1;i<N;i++){p[i]=i;Size[i]=1;dist[i]=0;}int t=0;cin>>t;while(t--){char op;int i=0,j=0;cin>>op>>i>>j;if(op=='M'){int pi=find(i),pj=find(j);if(pi!=pj){p[pi]=pj;  //将pi集合放到pj集合中dist[pi]=Size[pj];Size[pj]+=Size[pi];  //更新pj集合 元素个数}}else if(op=='C'){int pi=find(i),pj=find(j);if(pi==pj){//若i==j 则 距离为0   否则 abs()printf("%d\n",max(0,abs(dist[i]-dist[j])-1));}else{printf("-1\n");}}}return 0;
}

239. 奇偶游戏

链接: https://www.acwing.com/problem/content/description/241/

小 A 和小 B 在玩一个游戏。
首先,小 A 写了一个由 0 和 1 组成的序列 S,长度为 N。
然后,小 B 向小 A 提出了 M 个问题。
在每个问题中,小 B 指定两个数 l 和 r,小 A 回答 S[l∼r] 中有奇数个 1 还是偶数个 1。
机智的小 B 发现小 A 有可能在撒谎。
例如,小 A 曾经回答过 S[1∼3] 中有奇数个 1,S[4∼6] 中有偶数个 1,现在又回答 S[1∼6] 中有偶数个 1,显然这是自相矛盾的。
请你帮助小 B 检查这 M 个答案,并指出在至少多少个回答之后可以确定小 A 一定在撒谎。
即求出一个最小的 k,使得 01 序列 S 满足第 1∼k 个回答,但不满足第 1∼k+1 个回答。
输入格式
第一行包含一个整数 N,表示 01 序列长度。
第二行包含一个整数 M,表示问题数量。
接下来 M 行,每行包含一组问答:两个整数 l 和 r,以及回答 even 或 odd,用以描述 S[l∼r] 中有偶数个 1 还是奇数个 1。
输出格式
输出一个整数 k,表示 01 序列满足第 1∼k 个回答,但不满足第 1∼k+1 个回答,如果 01 序列满足所有回答,则输出问题总数量。
数据范围
N≤10^9,M≤5000
输入样例:
10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd
输出样例:
3

思路1:
我们观察数据范围,发现为10^9的空间,过于庞大,所以会使用离散化的技巧 来进行优化。

我们思考题目,发现S其实一个前缀和数组,S[i]表示 i位置及其前面所含的1的个数 ; [l,r]范围的1的个数 即为S[ r ]- S [ l-1 ]

[ l,r ] 如果有奇数个1 ,则 S[ r ]和S[ l-1 ] 奇偶性必不相同,两者一个为奇数,另一个必定为偶数

倘若[l,r]范围内有偶数个1,则则 S[ r ]和S[ l-1 ] 奇偶性相同,一个为奇数,另一个必定为奇数 或者一个为偶数,另一个必定为偶数

通过上面的描述,我们发现,输入的回答,可以使用条件集合进行表示
对于x位置而言 x数字表示S[x]为偶数 x+Base 数字表示S[x]位置为奇数
注:Base其实是一个不妨碍的整数

集合当中存放必然成立的条件

我们可以通过代码来回顾一下这个方案

代码实现:

#include<iostream>
#include<string>
#include<unordered_map>
using namespace std;
const int N=5000*4+10;// 将Base设置为[x,y] 和[x+Base,y+Base] 区间互不影响的数值即可
int Base=N/2;int p[N];//进行离散化处理
unordered_map<int,int>  map;
int get(int x){static int t=0;if(map.count(x)==0){map[x]= ++t;}return map[x];
}//并查集 find 函数
int find(int x){if(p[x]!=x){p[x]=find(p[x]);}return p[x];
}int main(){//初始化并查集for(int i=0;i<N;i++){p[i]=i;}int n=0,m=0;scanf("%d%d",&n,&m);int result=m;for(int i=1;i<=m;i++){int l=0,r=0;string type;cin>>l>>r>>type;int x=get(l-1),y=get(r);if(type=="even"){//区间内有偶数个1 则 S[x] 和S[y] 奇偶性相同,//如果不同,必不合法if(find(x+Base)==find(y)||find(x)==find(y+Base)){result=i-1;break;}//S[x] 和S[y] 奇偶性相同, //则 S[x]为奇数则S[y] 必为奇数  x+Base表示S[x]为奇数//   S[x]为偶数则S[y] 必偶数p[find(x)]=find(y);p[find(x+Base)]=find(y+Base);}else{//区间内有奇数个1 则 S[x] 和S[y] 奇偶性不同,//如果相同,必不合法if(find(x)==find(y)||find(x+Base)==find(y+Base)){result=i-1;break;}//S[x] 和S[y] 奇偶性不同, //则 S[x]为奇数则S[y] 必为偶数 //   S[x]为偶数则S[y] 必为奇数p[find(x+Base)]=find(y);p[find(x)]=find(y+Base);}}printf("%d\n",result);return 0;
}

思路2:

带边权的并查集:
我们观察数据范围,发现为10^9的空间,过于庞大,所以会使用离散化的技巧 来进行优化。

我们思考题目,发现S其实一个前缀和数组,S[i]表示 i位置及其前面所含的1的个数 ; [l,r]范围的1的个数 即为S[ r ]- S [ l-1 ]

[ l,r ] 如果有奇数个1 ,则 S[ r ]和S[ l-1 ] 奇偶性必不相同,两者一个为奇数,另一个必定为偶数

倘若[l,r]范围内有偶数个1,则则 S[ r ]和S[ l-1 ] 奇偶性相同,一个为奇数,另一个必定为奇数 或者一个为偶数,另一个必定为偶数

d[x] 表示 x 与根节点 的 奇偶性关系,0表示奇偶性相同,1表示奇偶性不同

[x,y]区间若有奇数个1,则S[x-1] 与S[y ]奇偶性不同
若 px=py 则d[x] ^ d [y] ==1 则无矛盾 ;d[ x ] ^d[ y ] == 0 则有矛盾

[x,y]区间若有偶数个1,则S[x-1] 与S[y ]奇偶性相同
若 px=py 则d[x] ^ d [y] ==0 则无矛盾 ;d[ x ] ^d[ y ] == 1 则有矛盾

问题一:
[x,y]区间若有奇数个1,则S[x-1] 与S[y ]奇偶性不同
若px!=py,则需要合并区间 根节点p[px]=py 而d[ ] 如何更新呢?

问题二:
[x,y]区间若有偶数个1,则S[x-1] 与S[y ]奇偶性相同
若px!=py,则需要合并区间 根节点p[px]=py 而d[ ] 如何更新呢?


对于问题一而言:
我们希望d[x] ^ d[ y ] ^ ? =1
倘若d[ x ]^d[y ] =0 则?=1 而 d[x] ^d[y ] = 1 的话,则 ?=0
因此? =d [ x ] ^d[ y ]^ 1

同理:
对于问题一而言:
我们希望d[x] ^ d[ y ] ^ ? =0
倘若d[ x ]^d[y ] =0 则?=0 而 d[x] ^d[y ] = 1 的话,则 ?=1
因此? =d [ x ] ^d[ y ]

我们可以通过代码来回顾一下这个方案

代码实现:

#include<iostream>
#include<cstring>
#include<unordered_map>
#include<string>
using namespace std;const int N=5000*2+10;int p[N],d[N];//离散化处理
unordered_map<int,int> map;
int get(int x){static int t=1;if(map.count(x)==0){map[x]=t++;}return map[x];
}int find(int x){if(p[x]!=x){int root=find(p[x]);d[x]^=d[p[x]];p[x]=root;}return p[x];
}
int main(){for(int i=1;i<N;i++){p[i]=i;}int n=0,m=0;scanf("%d%d",&n,&m);for(int i=1;i<=m;i++){int l=0,r=0;string type;cin>>l>>r>>type;// 我们要判断 l-1和r位置的奇偶性int x=get(l-1),y=get(r);//偶数为0 ,奇数为1int t=0;if(type=="odd"){t=1;}int px=find(x),py=find(y);if(px!=py){p[px]=py;d[px]=d[x]^d[y]^t;}else{if( (d[x]^d[y])!= t){printf("%d\n",i-1);return 0;}}}printf("%d\n",m);return 0;
}

代码模板:

按秩合并+路径压缩

const int N ; //并查集中的节点个数int p[N];  //存放节点所在树的根节点
int rank[N] ;//存放秩的数组,每个集合的秩进存放在根节点
void init(){//初始化,刚开始每个节点都是一棵树,根节点就是它自己//每个节点的秩为1for(int i=1;i<=N;i++){p[i]=i;rank[i]=1; }
}int Find(int x){if(p[x]!=x){//路径压缩 优化p[x]= find(p[x]);}return p[x];
}
void Union(int x,int y){// 找到x,y所在树的根节点int px=find(x),py=find(y);//根节点不相同,则不是一棵树,进行合并if(px!=py){// 按秩合并 优化if(rank[px]<=rank[py]){p[px]=py;//两个集合的秩相同,合并后的集合在原基础上加1//原因如下图所示rank[py]+=(rank[x]==rank[y]?1:0);}else{p[py]=px;}}
}

维护每个集合的大小

const int N ; //并查集中的节点个数int p[N];  //存放节点所在树的根节点
int size[N] ;//根节点位置存放集合大小
void init(){//初始化,刚开始每个节点都是一棵树,根节点就是它自己//每个集合的大小为1for(int i=1;i<=N;i++){p[i]=i;size[i]=1;}
}int Find(int x){if(p[x]!=x){//路径压缩 优化p[x]= find(p[x]);}return p[x];
}
void Union(int x,int y){// 找到x,y所在树的根节点int px=find(x),py=find(y);p[px]=py;size[py]+=size[px];
}

维护到根节点距离

const int N ; //并查集中的节点个数int p[N];  //存放节点所在树的根节点
int dist[N] ;//到根节点的距离
void init(){//初始化,刚开始每个节点都是一棵树,根节点就是它自己for(int i=1;i<=N;i++){p[i]=i;dist[i]=0;}
}int Find(int x){if(p[x]!=x){//路径压缩 优化int root= find(p[x]);dist[x]+=dist[p[x]];p[x]=root;}return p[x];
}
void Union(int x,int y){// 找到x,y所在树的根节点int px=find(x),py=find(y);p[px]=py;size[px] ? 根据具体情况进行初始化//合并的时候我们仅更新px到合并后根节点py的距离//原px的其他节点会在find函数中自动更新距离
}

[数据结构、读书笔记、C++] 并查集详解相关推荐

  1. 并查集详解 ——图文解说,简单易懂(转)特别好玩

    并查集详解 --图文解说,简单易懂(转) 2016年03月10日 17:38:08 阅读数:6931 标签: 并查集数据结构并查集算法图文解说 更多 个人分类: 算法--并查集 并查集是我暑假从高手那 ...

  2. 数据结构(八):并查集详解 (多图+动图)

    目录 一.什么是并查集 二.并查集的存储结构 三.并查集的基本操作 (一)初始化 (二)Find操作 (三)Union操作 四.并查集的优化 (一)Union操作优化(小树并入大树) (二)Find操 ...

  3. 【数据结构之并查集】并查集详解(零基础入门,超级有趣的!!!)

    转的一个超级有意思,好懂的并查集解释, 膜拜大神~~ 故事读完,并查集就会了~~~~~ 江湖上散落着各式各样的大侠,有上千个之多.他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的 ...

  4. - 并查集详解(第二节)

    以下是并查集思路详解: 一:概念 并查集处理的是"集合"之间的关系.当给出两个元素的一个无序数对(a,b)时,需要快速"合并"a和b分别所在的集合,这期间需要反 ...

  5. 7-3 最小生成树-kruskal (10 分)(思路+详解+并查集详解+段错误超时解决)宝 Come

    一:前言 本题需要用到并查集的知识,建议先学完并查集后再看看本题 二:题目 题目给出一个无向连通图,要求求出其最小生成树的权值. 温馨提示:本题请使用kruskal最小生成树算法. 输入格式: 第一行 ...

  6. 拓扑排序 详解 + 并查集 详解 + 最小生成树详解

    若您发现本文有什么错误,请联系我,我会及时改正的,谢谢您的合作! 本文为原创文章,转载请注明出处 本文链接   : http://www.cnblogs.com/Yan-C/p/3943940.htm ...

  7. 并查集详解:UF——UF_Tree——UF_Tree_Weighted逐步优化

    目录 1 并查集简介 2 UF 2.1 UF(int N)构造方法实现 2.2 union(int p, int q)合并方法实现 2.3 代码实现(Java) 2.4 应用举例与复杂性分析 3 UF ...

  8. 并查集详解(从引入到代码)

    江湖上散落着各式各样的大侠,有上千个之多.他们没有什么正当职业,整天背着剑在外面走来走去,碰到和自己不是一路人的,就免不了要打一架.但大侠们有一个优点就是讲义气,绝对不打自己的朋友.而且他们信奉&qu ...

  9. 一个很有意思的并查集详解

    并查集是我暑假从高手那里学到的一招,觉得真是太精妙的设计了.以前我无法解决的一类问题竟然可以用如此简单高效的方法搞定.不分享出来真是对不起party了.(party:我靠,关我嘛事啊?我跟你很熟么?) ...

最新文章

  1. 怎样做网络推广浅谈如何更高效的提升关键词排名?你还不知道?
  2. Python Django 日期增减API
  3. 让我们的标签语义化成为一种习惯好处多多
  4. 算法高级(33)-拓扑排序-maven依赖关系的确定
  5. 想赚钱,赚大钱,必须要有商业思维
  6. 痞子衡嵌入式:极易上手的可视化wxPython GUI构建工具(wxFormBuilder)
  7. L1-016. 查验身份证-PAT团体程序设计天梯赛GPLT
  8. Android Camera数据流分析全程记录(overlay方式一)
  9. Web前端开发规范之文件存储位置规范
  10. ssh-copy-id非22端口的使用方法
  11. VS code入门笔记(一)常用标签介绍
  12. 国足0:2不敌韩国 淘汰赛将战泰国
  13. dubbo服务出现大量超时问题
  14. Ubuntu的root
  15. MSD3393/MSD3463 屏参及REG对照表
  16. Dell居然用EMS给我寄发票
  17. git 加速代理设置,单仓库设置代理,指定仓库设置单独代理
  18. 网络基础知识 | 协议 | TCP/IP分层模型
  19. 如何成为一颗 GitHub Star
  20. GLU(Gated Linear Units)

热门文章

  1. Google 翻译中更稳定的实时语音翻译
  2. “侵入”住宅的北京农夫世家餐饮管理有限公司青蛇
  3. 6.4.1最小生成树
  4. 基于eNSP的IPv6校园网络规划与设计_综合实验
  5. x7系统怎么锁定计算机,CorelDRAW X7如何锁定对象和解锁对象
  6. 关于跨境支付,你了解多少?
  7. 别迷糊啦!经常出差却不知道出差补贴是否要交个税???
  8. Android studio项目中LitePal配置详细过程与使用
  9. 【Nginx】Docker配置ngnix,实现同服务器ip多站点多域名
  10. 手把手教你搭建搭建JPress