一、模型建立
本质就是一个数组,数组的下标对应节点的编号,数组的值对应对应编号的节点的父节点。规定根节点的父节点是自己。
规定三个集合的根节点分别是1 4 6
二、并查集操作并实现
并查集主要操作:查找一个节点的父节点,判断两个节点是不是在一个集合,合并两个节点所在的两个集合。
这里的第二第三个操作是基于第一个查找父节点的,但是查找父节点的操作有一个路径优化,所以最后再讲。
1、判断两个节点是不是在一个集合
看看两个节点的父节点是不是相同就行了。
bool issame(int x, int y)
{return find(x) == find(y);
}
2、合并两个节点所在的两个集合
让两个节点的父节点合并,即一个父节点是另一个父节点的父节点。
void U(int x, int y)
{int root1 = find(x);int root2 = find(y);a[root1] = root2;
}
3、查找一个节点的父节点
根据数组特性,只要不是数值等于下标,那就不是父节点,还需要找数值的父节点,即找当前节点的父亲的父亲。
int find(int x)
{if (a[x] == x)return x;return find(a[x]);
}
但是如果一开始的结构是近似单支树,那么每次查找的效率就会降低成O(N)
路径压缩:在遍历到根节点之后,回溯时把回溯过程中经历到的孩子节点的父节点全部修改成根节点,这样就压缩成了2层。
int find(int x)
{if (a[x] == x)return x;// 路径压缩:根的后代的父节点直接改成根return a[x] = find(a[x]);
}
三、例题
P3367 【模板】并查集 - 洛谷
#include "bits/stdc++.h"
using namespace std;
const int N = 2 * 1e5 + 10;
int a[N];
// 查找
int find(int x)
{if (a[x] == x)return x;// 路径压缩:根的后代的父节点直接改成根return a[x] = find(a[x]);
}
// 合并
void U(int x, int y)
{int root1 = find(x);int root2 = find(y);a[root1] = root2;
}// 判断
bool issame(int x, int y)
{return find(x) == find(y);
}int main()
{int n, m;cin >> n >> m;// 1.初始化所有点是单独一个集合,根是自己for (int i = 1; i <= n; i++)a[i] = i;while (m--){int op, x, y;cin >> op >> x >> y;if (op == 1)U(x, y);elseissame(x, y) == true ? cout << "Y" << endl : cout << "N" << endl;}return 0;
}