目录
普通并查集
解决的问题:朋友的朋友是朋友
解题报告:https://blog.csdn.net/qq_54886579/article/details/119395026
模板
#include<cstdio>
#include<iostream>
using namesapce std;
/*并查集: 合并(Union)、查找(Find)、集合(Set)
1 合并: 合并两个集合
2 查找: 判断两个元素是否在一个集合 */
const int N=1e6+1;
int father[N];//并查集用数组实现,根节点的话father[i]=i
int rank[N];//记录秩的数组,由于秩是用来比较的,所以秩初始化为0即可,不必初始化为1 秩的维护在连接父亲时rank[i]++即可
//初始化:一开始, 每个元素都是独立的一个集合, 因此需要令所有 father[i]等于 i:
void inf()
{
for(int i=1;i<=N;i++)
{
father[i]=i;
}
}
/*查找:由于规定同一个集合中只存在一个根结点, 因此查找操作就是对给定的结点寻找其根结点的过程。
实现的方式可以是递推或是递归 ,但是其思路都是一样的, 即反复寻找父亲结点,直到找到根结点(即 father[i] = i 的结点)。 */
//递推实现 findFather函数返回元素x所在集合的根结点
int findFather(int x)
{
while(x!=father[x])//如果不是根结点, 继续循环
{
x=father[x];//获得自己的父亲结点
}
return x;
}
//递归实现
int findFather_(int x)
{
if(x==father[x]) return x;//递归边界
else return findFather_(father[x])//否则, 递归判断x的父亲结点是否是根结点
}
/*举例 [4,2,1] fF(1) 执行fF(2) 执行fF(4) 到达边界 返回4给fF(2) fF(2)=4 返回4给fF(1) fF(1)=4
在递归过程中 只有root的值被返回 */
/*合并: 合并是指把两个集合合并成一个集合
题目中一般给出两个元素, 要求把这两个元素所在的集合合并。
具体实现上一般是先判断两个元素是否属于同一个集合, 只有当两个元素属于不同集合时才合并
合并的过程一般是把其中一个集合的根结点的父亲指向另一个集合的根结点*/
/*步骤:一: 对于给定的两个元素a,b,判断它们是否属于同一集合。可以调用上面的查找函数,对这两个元素a、b分别查找根结点,然后再判断其根结点是否相同。
二:合并两个集合:在一中已经获得了两个元素的根结点 faA 与 faB, 因此只需要把其中一个的父亲结点指向另一个结点。
例如可以令 father[faA] = faB , 当然反过来令 father[faB]= faA 也是可以的, 两者没有区别。*/
//一般我们采用按秩合并
void Union_(int a,int b)//U务必大写,因为union是个关键字
{
int faA=findFather(a);
int faB=findFather(b);
if(faA==faB) return;//判断它们是否属于同一集合
else{
father[faB]=faA;//把其中一个的父亲结点指向另一个结点
}
}
//按秩合并 秩即树的深度,把秩少的根节点a接到秩多的根节点b上,即a->b,可以减少合并后的数的秩
//如果不能破坏树形结构,用按秩合并,否则路径压缩和按秩合并能连用
void Union(int i,int j)
{
i=findfather(i);
j=findfather(j);
if(i==j) return ;//i与j同属一个集合
if(rank[i]>rank[j]) father[j]=i;//i的秩较大,j接到i上
else
{
if(rank[i]==rank[j]) rank[j]++; //i与j秩相等,随意接,这里i接到j上,j的秩++
father[i]=j;
}
}
//路径压缩:把当前查询结点的路径上的所有结点的父亲都指向根结点, 查找的时候就不需要一直回溯去找父亲了, 查询的复杂度可产以降为O(1)
/*步骤:一:按原先的写法获得 x 的根结点 root
二:重新从x开始走一遍寻找根结点的过程,把路径上经过的所有结点的父亲全部改为根结点root */
int findfather(int x)
{
//由于x在下面的while中会变成根结点, 因此先把原先的x保存一下
int temp=x;
while(x!=father[x]){//寻找根节点
x=father[x];
}
//到这里, x存放的是根结点。 下面把路径上的所有结点的father都改成根结点
while(temp!=father[temp])
{
int t=temp;//因为temp要被father[temp]覆盖, 所以先保存temp的值, 以修改father[temp]
temp=father[temp];
father[t]=x;//将原先的结点temp的父亲改为根结点x
}
return x; //返回根结点
}
//递归版
int findfather_(int x)
{
if (x != father[x])
{
father[x] = findfather_(father[x]);
}
return father[x];
}
//简洁版
int Find(int i) {//f[i]是全局变量
return f[i]==i ? f[i] : f[i]=Find(f[i]);
}
int main(){
return 0;
}
带权并查集
解决问题:悖论问题
解题报告:https://blog.csdn.net/qq_54886579/article/details/119399304
模板
//有的时候在这些边中添加一些额外的信息可以更好的处理需要解决的问题,在每条边中记录额外的信息的并查集就是带权并查集
//https://blog.csdn.net/yjr3426619/article/details/82315133
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e6+1;
int father[N];//并查集用数组实现,根节点的话father[i]=i
int rank[N];//记录秩
int value[N];//记录权值 ,权值在不同背景下有不同意义,通常理解为该节点到父/根节点的偏移量,所以根节点权值为0 或者权值描述的是边而不是节点!
void inf(int n)//初始化
{
for(int i=1;i<=N;i++) father[i]=i;
}
//路径压缩 递归实现 无法以较小代价用递推实现
int findFather(int x)
{
if (x != father[x])
{
int t = father[x];
father[x] = findFather(father[x]);
value[x] += value[t];
}
return father[x];
}
/*合并 方程需要具体问题具体分析, 通常以向量加以理解,且序号小的是序号大的后代,例如
x->fx=v[x] y->fy=v[y] 给出y->x=s 求fy->fx——v[fy]
即y->x->fx == y->fy->fx 所以s+v[x]==v[y]+v[fy],v[fy]=s+v[x]-v[y]
*/
{//代码段如下
int px = find(x);
int py = find(y);
if (px != py)
{
parent[px] = py;
value[px] = s - value[x] + value[y] ;//方程具体问题具体分析
}
}
int main(){
}
种类并查集
解决问题:敌人的敌人是朋友
解题报告:
两种:https://blog.csdn.net/qq_54886579/article/details/119423864
三种:https://blog.csdn.net/qq_54886579/article/details/119431899
模板以上面两篇文章的题目为背景呈现
可持久化并查集
解决问题:访问历史版本并查集(撤销)
原理:https://blog.csdn.net/qq_54886579/article/details/119768391