1.定义

  1. 并查集是一种树型的数据结构,用于处理一些不相交集合(disjoint sets)的合并及查询问题。

  2. 并查集通常包含两种操作

  • 查找(Find):查询两个元素是否在同一个集合中
  • 合并(Union):把两个不相交的集合合并为一个集合

2.代码

(1)初始化

#define MAXN 100
int Parent[MAXN];

void init(int n){
for(int i=0;i<n;i++){//每个人初始父节点为自身
Parent[i]=i; //存放每个结点的结点(或双亲结点)
}
}

(2)查找

//查询根结点
int find(int x){
if(Parent[x]==x)
return x;
else
return find(Parent[x]);
}

(3)合并

//合并,把 j 合并到 i 中去,就是把j的双亲结点设为i
void merge(int i,int j){
Parent[find(j)] = find(i);
}

(4)路径压缩

减少路径长度,将集合扁平化,每个元素父节点设为根节点,提高效率

查找

//查询根结点
int find(int x){
if(Parent[x]==x)
return x;
else{
Parent[x]=find(Parent[x]);
return Parent[x];
}
}


//简化版
int find(int x)//查询根节点的同时将x的父节点设置为根节点
{
return x == Parent[x] ? x : (Parent[x] = find(Parent[x]));
}

完整代码

#include <iostream>
using namespace std;

#define MAXN 100
int Parent[MAXN];

//初始化,现在各自为王,自己就是一个集合
void init(int n){
for(int i=0;i<n;i++){
Parent[i]=i;
}
}

//查询根结点
int find(int x){
if(Parent[x]==x)
return x;
else{
Parent[x]=find(Parent[x]); //顺便把双亲结点也设置为根结点,路径压缩
return Parent[x];
}
}

//合并,把 j 合并到 i 中去,就是把j的双亲结点设为i
void merge(int i,int j){
Parent[find(j)] = find(i);
}

int main()
{
return 0;
}

(5)按秩合并

显然的,简单的树应合并到复杂的树上从而使得树的深度较小,即按秩合并的思想;

初始化

void init(int n){
for(int i=0;i<n;i++){//rank代表根节点所在树的深度,只有根节点时为1
Parent[i]=i;
Rank[i]=1;
}
}

合并

//合并
void merge(int i,int j){
int x = find(i),y = find(j); //分别找到结点i和结点j的根节点
if(x!=y){//根节点不同才需要合并
if(Rank[x] < Rank[y]){ //如果以x作为根结点的子树深度小于以y作为根结点的子树的深度,则把x合并到y中
Parent[x]=y;
}
else if((Rank[x] > Rank[y])){
Parent[y]=x;
}
else{ //(Rank[x] == Rank[y])如果深度相同,以x为根结点的子树深度+1
Parent[x]=y;
Rank[x]++;
}
}
}

(6)模板代码

//并查集类
class DisJointSetUnion
{
private:
// 所有根结点相同的结点位于同一个集合中
vector<int> parent; // 双亲结点数组,记录该结点的双亲结点,用于查找该结点的根结点
vector<int> rank; // 秩数组,记录以该结点为根结点的树的深度,主要用于优化,在合并两个集合的时候,rank大的集合合并rank小的集合

public:
DisJointSetUnion(int n) //构造函数
{
for (int i = 0; i < n; i++)
{
parent.push_back(i); //此时各自为王,自己就是一个集合
rank.push_back(1); //rank=1,此时每个结点自己就是一颗深度为1的树
}
}

//查找根结点
int find(int x)
{
if(x==parent[x])
return x;
else
{
parent[x] = find(parent[x]); // 路径压缩, 遍历过程中的所有双亲结点直接指向根结点,减少后续查找次数
return parent[x];
}
}

void merge(int x,int y)
{
int rx = find(x); //查找x的根结点,即x所在集合的代表元素
int ry = find(y);

if (rx != ry) //如果不是同一个集合
{
if (rank[rx] < rank[ry]) //rank大的集合合并rank小的集合
{
swap(rx, ry); //这里进行交换是为了保证rx的rank大于ry的rank,方便下面合并
}

parent[ry] = rx; //rx 合并 ry

if (rank[rx] == rank[ry])
rank[rx] += 1;
}
}
};

3.例题

2069. 网络分析 - AcWing题库

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010;

int n, m;
int p[N], d[N];

int find(int x)
{
if (p[x] == x || p[p[x]] == p[x]) return p[x];
int r = find(p[x]);
d[x] += d[p[x]];
p[x] = r;
return r;
}

int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
while (m -- )
{
int t, a, b;
scanf("%d%d%d", &t, &a, &b);
if (t == 1)
{
a = find(a), b = find(b);
if (a != b)
{
d[a] -= d[b];
p[a] = b;
}
}
else
{
a = find(a);
d[a] += b;
}
}

for (int i = 1; i <= n; i ++ )
if (i == find(i)) printf("%d ", d[i]);
else printf("%d ", d[i] + d[find(i)]);
puts("");

return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/603227/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。