文章目录
并查集 1
一.并查集—合并+查找
并查集两个操作: 近乎0(1)时间复杂度内快速维护这两个操作
- 将两个集合合并
- 询问两个元素是在一个集合中
- 维护每个集合中元素的个数 见连通块中的点的个数
- 维护每个集合中元素与根节点的关系 见食物链
并查集的最本质是find函数,记这个就行了
二.基本原理
每个集合用一棵树来表示。树根的编号就是整棵树的编号。每个节点储存它的父节点,p[x]表示x的父节点
问题1. 如何判断树根:if(p[x] == x)
问题2. 如何求x的集合标号: while(p[x] != x) x = p[x];
问题3. 如何合并两个集合: p[find(x)] = find(y)
优化-路径压缩
我找一次后,所有路径上的点指向跟节点
第一次求要三次,第二次就只需要一次
模版题 AcWing 836.合并集合
版本1 简练
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 100010;
int n, m;
int p[N];
int find(int x)
{ // 返回x的祖宗节点 + 路径压缩
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
int main() {
string c; // 用string, 不会遇到M,Q1,Q2这种询问的麻烦
int a, b;
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
p[i] = i;
}
for (int i = 0; i < m; i ++) {
cin >> c >> a >> b;
if (c == "M") p[find(a)] = find(b); // union操作 合并
else {
if (find(a) == find(b))
cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
版本2 用了UnionFind类 vector初始化遇到0比较麻烦
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
class UnionFind {
public:
vector<int> father;
UnionFind(int num) {
for (int i = 0; i <= num; i ++) {
father.push_back(i);
}
}
int Find(int n) {
if (father[n] == n) return n;
father[n] = Find(father[n]);
return father[n];
}
void Union(int a, int b) {
int fa = Find(a);
int fb = Find(b);
father[fb] = fa;
}
};
int main() {
int n, m;
char c;
int a, b;
cin >> n >> m;
UnionFind UF(n);
for (int i = 0; i < m; i ++) {
cin >> c >> a >> b;
if (c == 'M') UF.Union(a, b);
else {
if (UF.Find(a) == UF.Find(b)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
return 0;
}
AcWing 837.连通块中点的个数
// 三种操作都包含了——特别是维护每个集合的个数
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
const int N = 100010;
int n, m;
int p[N], size[N];
// 只记录根结点的size
int find(int x) { // 返回x的祖宗节点 + 路径压缩
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main() {
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
//freopen("out.txt","w",stdout);
#endif
string c; // 用string, 不会遇到M,Q1,Q2这种询问的麻烦
int a, b;
cin >> n >> m;
// 初始化
for (int i = 1; i <= n; i ++) {
p[i] = i;
size[i] = 1;
}
// 分三种情况
for (int i = 0; i < m; i ++) {
cin >> c;
if (c == "C") {
cin >> a >> b;
if (find(a) != find(b)) { // 如果已经连通了,就不用再加结点数了,因为我们只看根的size
size[find(b)] += size[find(a)];
}
p[find(a)] = find(b); // union操作 合并
} else if (c == "Q1") {
cin >> a >> b;
if (find(a) == find(b)) puts("Yes");
else puts("No");
} else {
cin >> a;
cout << size[find(a)] << endl;
}
}
return 0;
}
AcWing 240.食物链
记录与根节点的关系
/*
0->1->2->0 0是根节点
距离d:记录当前节点到根节点的距离
余1:可以吃根节点
余2:可以被根节点吃 x
余0:与根节点是同类 y
x吃y:x - y mod 3 为1
x被y吃:x - y mod 3 为2
*/
#include <iostream>
using namespace std;
const int N = 50010;
int n, m;
int p[N], d[N];
int find(int x)
{
if (p[x] != x)
{
int t = find(p[x]); // 递归语句必须写在前面,这样可以保证你先到达了根节点,然后倒过来一步步执行后面的语句
// 因为p[a] = b, p[b] = c;你合并了三棵树,这是你再取a树中的点,find(x)它的d[p[x]]需要先更新,因为d[p[x]]还存的是x到a的距离
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
// int find(int x) {
// if (p[x] != x) {
// d[x] += d[p[x]]; // 这样写距离会变短,比如你Union了三棵树 d[p[x]]一直没被更新 这个问题我想了很久才搞明白,每次碰到递归就头疼
// p[x] = find(p[x]);
// }
// return p[x];
// }
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
p[i] = i;
}
int res = 0;
while (m --) {
int t, x, y;
cin >> t >> x >> y;
if (x > n || y > n) res ++;
else {
int px = find(x), py = find(y);
if (t == 1) { // 判断是否同类
if (px == py && (d[x] - d[y]) % 3 != 0) res ++;
else if (px != py) {
p[px] = py;
d[px] = d[y] - d[x];
}
} else { // t == 2 给出谁吃谁
if (px == py && (d[x] - d[y] - 1) % 3 != 0) res ++;
else if (px != py) { // 不是一个集合,因此需合并
p[px] = py;
d[px] = d[y] - d[x] + 1; // (d[x] + ? - d[y]) mod 3 == 1
}
}
}
}
cout << res << endl;
return 0;
}
并查集 2
并查集是面试笔试和竞赛中非常常用的一个数据结构算法,因为它代码比较精简,但思维含量高
并查集两个操作: 近乎0(1)时间复杂度内快速维护这两个操作
- 将两个集合合并
- 询问两个元素是在一个集合中
一:并union 查 find
判断两个元素是不是在同一个集合就看他们的老大是不是同一个。
1,2; 3,4;两个集合 2和4合并需要选出一个新的老大。
4的原老大是3,现在他最大的老大是1,相当于他原来是个小公司被大公司合并了。所以这个小公司的所有人都要服从于最后的大老板
所以判断一个元素是不是老大就看它的箭头是不是指向它自己。
二:find和union
find:一直顺着箭头找,直到找到最大的boss
union:先找两个元素的老大,再选一个作为最终的老大
三:路径压缩
第一次一步一步找到了最终boss,那么此时建立一个直接到最终boss的箭头,那之后要做查询时就不需要再通过boss的boss的boss一步步找到最终boss了,相当于你直接和最终boss建立了联系。 这就是路径压缩
并查集模板
class UnionFind {
public:
vector<int> father;
UnionFind(int num) {
for (int i = 0; i < num; i ++) {
father.push_back(i);
}
}
int Find(int n) {
if(father[n] == n) return n;
father[n] = Find(father[n]);
return father[n];
}
bool Union(int a, int b) {
int fa = Find(a);
int fb = Find(b);
father[fb] = fa;
return fa == fb; // 判断两个点是否连通
}
};
示例代码
class UnionFind {
public:
vector<int> father;
// num表示元素个数
// 这里的UnionFind定义在这里其实是为了方便初始化并查集
// 如UnionFind UF(n);
UnionFind(int num) {
for (int i = 0; i < num; i ++) {
father.push_back(i); // 箭头指向自己
}
}
// 4->3->1->1 finish
int Find(int n) {
// 非递归
while(father[n] != n) {
n = father[n];
}
return n;
}
int Find(int n) {
// 递归
if (father[n] == n) return n;
return Find(father[n]);
}
// 不仅返回了4的最终boss,还让4和1建立了一个联系,方便下次直接找1
int Find(int n) {
// 递归 + 路径压缩
if(father[n] == n) return n;
father[n] = Find(father[n]);
return father[n]; // 这里改为Find(father[n])也是可以的,但是会多占用很多空间
}
void Union(int a, int b) {
int fa = Find(a); // 2的原老大是1
int fb = Find(b); // 4的原老大是3
father[fb] = fa; // 让3指向1
}
};
LeetCode 547.FriendCircles
// 版本一 UnionFind初始化
class UnionFind {
public:
vector<int> father;
UnionFind(int num) {
for (int i = 0; i < num; i ++) {
father.push_back(i);
}
}
int Find(int n) {
// 递归 + 路径压缩
if(father[n] == n) return n;
father[n] = Find(father[n]);
return father[n];
}
void Union(int a, int b) {
int fa = Find(a); // 2的原老大是1
int fb = Find(b); // 4的原老大是3
father[fb] = fa; // 让3指向1
}
};
class Solution {
public:
int findCircleNum(vector<vector<int>> &M) {
int n = M.size();
UnionFind UF(n);
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
if (M[i][j] == 1) UF.Union(i, j);
}
}
int res = 0;
for (int i = 0; i < n; i ++) {
if (UF.Find(i) == i) {
res ++;
}
}
return res;
}
};
LeetCode 200.NumberOfIslands
// 并查集
// 向四个方向进行Union操作,注意两个参数的先后,后面的服从前面的
class UnionFind {
public:
vector<int> father;
UnionFind(int num) {
for (int i = 0; i < num; i ++) {
father.push_back(i);
}
}
int Find(int n) {
if (father[n] == n) return n;
father[n] = Find(father[n]);
return father[n];
}
void Union(int a, int b) {
int fa = Find(a);
int fb = Find(b);
father[fb] = fa;
}
};
int encode(int i, int j, int m) {
return i * m + j;
}
class Solution {
public:
int numIslands(vector<vector<char>> &grid) {
if (!grid.size() || !grid[0].size()) return 0;
int n = grid.size(), m = grid[0].size();
UnionFind UF(n * m);
// 合并
int dx[] = {1, 0, -1, 0}, dy[] = {0, -1, 0, 1};
for (int i = 0; i < n; i ++) {
for (int j = 0; j < m; j ++) {
if (grid[i][j] == '1') {
for (int d = 0; d < 4; d ++) {
int x = dx[d] + i, y = dy[d] + j;
if (x >= 0 && x < n && y >= 0 && y < m && grid[x][y] == '1') {
UF.Union(encode(i, j, m), encode(x, y, m));
}
}
}
}
}
// 查找
int res = 0;
for (int i = 0; i < n; i ++) {
for (int j = 0; j < m; j ++) {
if (grid[i][j] == '1' && UF.Find(encode(i, j, m))== encode(i, j, m)) {
res ++;
}
}
}
return res;
}
};
// dfs算法
void dfs(vector<vector<char>> &grid, int a, int b) {
//因为要修改原来的grid,所以这里必须用引用
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
int m = grid.size(), n = grid[0].size();
grid[a][b] = '0';
for (int i = 0; i < 4; i++) {
int x = a + dx[i], y = b + dy[i];
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == '1') {
dfs(grid, x, y);
}
}
return;
}
class Solution {
public:
int numIslands(vector<vector<char>> &grid) {
int ans = 0;
if (!grid.size() || !grid[0].size()) return 0;
int m = grid.size(), n = grid[0].size();
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
ans++;
}
}
}
return ans;
}
};
LeetCode 684.Redundant Connection
// 判断两个顶点是否连通,看他们的祖先是不是相同
// 685进阶题
class UnionFind
{
public:
vector<int> father;
UnionFind(int num)
{
for (int i = 0; i < num; i++)
{
father.push_back(i);
}
}
int Find(int n)
{
if (father[n] == n)
return n;
father[n] = Find(father[n]);
return father[n];
}
bool Union(int a, int b)
{
int fa = Find(a);
int fb = Find(b);
father[fb] = fa;
return fa == fb; // 判断两个点是否连通
}
};
class Solution
{
public:
vector<int> findRedundantConnection(vector<vector<int>> &edges)
{
int n = edges.size();
UnionFind UF(n);
for (int i = 0; i < n; i++)
{
int x = edges[i][0], y = edges[i][1];
// 初始化是0,1..n-1,所有下标要先减1,不然会越界
if (UF.Union(x - 1, y - 1))
{
return {x, y};
}
}
return {1, 1};
}
};
来源:CSDN
作者:Wilson790
链接:https://blog.csdn.net/qq_43827595/article/details/103460593