并查集2.0
并查集1.0部分
视频地址:哔哩哔哩up主正月点灯笼
参考文章:https://www.cnblogs.com/asdfknjhu/p/12515480.html
先展示一下正月大神的代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define VERTICES 6
int find_root(int x, int parent[]) // 找到根节点
{
int x_root = x;
while (parent[x_root] != -1)
{
x_root = parent[x_root];
}
return x_root;
}
//未压缩路径版本的union_vertices
int union_vertices(int x, int y, int parent[]) // 让两个集合合并
{
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if (x_root == y_root)
return 0;
else
{
parent[x_root] = y_root;
return 1;
}
}
int main(void)
{
int parent[VERTICES] = { 0 };
memset(parent, -1, sizeof(parent)); // 初始化,所有节点的祖先都是-1
int edges[6][2] = { {0,1},{1,2},{1,3},{2,4},{3,4},{2,5} }; // 边集
for (int i = 0; i < 6; i++)
{
int x = edges[i][0];
int y = edges[i][1];
if (union_vertices(x, y, parent) == 0)
{
printf("Cycle detected!\n");
system("pause");
exit(0);
}
}
printf("No cycle found.\n");
system("pause");
return 0;
}
//压缩路径版本的union_vertices
int union_vertices(int x, int y, int parent[]) // 让两个集合合并
{
int x_root = find_root(x, parent);
int y_root = find_root(y, parent);
if (x_root == y_root)
return 0;
else
{
if (Rank[x_root] > Rank[y_root])//Rank要整体添加,不要用rank,会造成和保留字冲突
{
father[y_root] = x_root;
}
else if(Rank[y_root] > Rank[x_root])
{
father[x_root] = y_root;
}
else
{
father[x_root] = y_root;
Rank[y_root]++;//记得每个节点的Rank要初始化
}
return 1;
}
}
我对并查集的理解,就是对于一堆节点的任意两个节点进行连接,要想判断连接之后是否会形成一个环,就是同过回溯节点到他的祖先节点,如果任意两个祖先节点一致,这时将两个节点连接起来就构成环了,反之则构不成环
下面给出几个例题并附上代码:
这几题的话都是套路,就当模板记下来就可了,实现主要功能在于几个函数,union,find,Initialization.
例题1:
4-3-1 并查集 朋友圈 (25 分)
#include<iostream>
#include<algorithm>
using namespace std;
int a[30005], parent[30005],sum[30005];
void Unite(int x, int y);//建立边,连接
int find(int x);
void Initialization(int n,int a[]);
int main()
{
int N, M;
cin >> N >> M;
Initialization(N,a);
for (int i = 0; i < M; i++)
{
int n;//每个社团人数
cin >> n;
for (int j = 0; j < n; j++) cin >> a[j];
for(int j=0;j<n;j++)
for (int t = j + 1; t < n; t++)
{
Unite(a[j], a[t]);
}
}
int maxn = 1;
for (int i = 0; i < N; i++)
{
if (sum[i] > maxn) maxn = sum[i];
}
cout << maxn;
return 0;
}
int find(int x)//往上回溯找根节点
{
if (parent[x] == x) return x;//每一个子节点的根节点
else return find(parent[x]);//***
}
void Initialization(int n, int a[])
{
for (int i = 0; i < n; i++)
{
parent[i] = i;//最开始每一个成员上溯都是自己
sum[i] = 1;//一开始每个人的朋友圈只有自己
}
}
void Unite(int x, int y)//没有压缩路径
{
int x_root = find(x);
int y_root = find(y);
if (x_root != y_root)//如果两个子节点的根节点不一样,则说明他们现在还不是一个集合里的,把他们两个连起来;
{
parent[y_root] = x_root;
sum[x_root] += sum[y_root];
}
}
例题2:
4-3-2 并查集 部落 (25 分)
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int child[10005], father[10005];
void Initialization();
void Unite(int x, int y);
int find_root(int x);
int main()
{
int N;//小圈子的人数
cin >> N;
Initialization();//初始化所有节点的父节点都是自己
map<int, int>m,club;//m是用来计算人数的,club是用来看部落个数的
for (int i = 0; i < N; i++)
{
int k;
cin >> k;//每个小圈子的人数
for (int j = 0; j < k; j++)
{
cin >> child[j];
m[child[j]]++;
}
for (int j = 0; j < k; j++)
for (int t = j+1; t < k; t++)
{
Unite(child[j], child[t]);
}
}
int count_number = 0;//一共有多少人
for (auto x : m)
{
if (x.second != 0)
{
father[x.first] = find_root(x.first);
count_number++;
}
else break;
club[father[x.first]]++;
}
int count_club=0;//一共几个部落
for (auto x : club)
if (x.second != 0) count_club++;
cout << count_number << " " << count_club << endl;
int Q,x,y;
cin >> Q;//查询次数
for (int i = 0; i < Q; i++)
{
cin >> x >> y;
if (father[x] == father[y]) cout << "Y" << endl;
else cout << "N" << endl;
}
return 0;
}
int find_root(int x)
{
if (father[x] == x) return x;
else return find_root(father[x]);
}
void Unite(int x, int y)
{
int x_root = find_root(x);
int y_root = find_root(y);
if (x_root != y_root)
{
father[y_root] = x_root;
}
}
void Initialization()
{
for (int i = 0; i < 10005; i++)
father[i] = i;
}
例题3:(这题就必须压缩路径了,不然一个点会因为超时过不了)
4-3-3 并查集 文件传输 (25 分)
#include<iostream>
#include<algorithm>
#include<map>
using namespace std;
int father[10005],Rank[10005];
void Initialization(int n);
void Unite(int x, int y);
int find_root(int x);
int main()
{
int n;
cin >> n;//计算机的个数
getchar();
Initialization(n);//初始化所有节点的父节点为自己本身
char c;
int x, y;
while (c = getchar())
{
//getchar();
if (c == 'S') break;
cin >> x >> y;
if (c == 'I') Unite(x, y);
else if (c == 'C')
{
if (find_root(x) == find_root(y)) cout << "yes" << endl;
else cout << "no"<< endl;
}
getchar();//这个不能丢,要把每行结尾的\n读掉,不然的话下一个c=\n.继续cin>>x>>y;有影响
}
map<int, int>m;
for (int i = 1; i <= n; i++)
{
int ancestor= find_root(i);
m[ancestor]++;
}
int count = 0;
for (auto x : m)
{
if (x.second != 0) count++;
else break;
}
if (count == 1) cout << "The network is connected." << endl;
else cout << "There are " << count << " components." << endl;
return 0;
}
int find_root(int x)
{
if (father[x] == x) return x;
else return find_root(father[x]);
}
void Unite(int x, int y)
{
int x_root = find_root(x);
int y_root = find_root(y);
if (x_root != y_root)
{
if (Rank[x_root] > Rank[y_root])//第一遍没有压缩路径,有一个点超时了,就试了一下压缩路径,就过了,行吧
{
father[y_root] = x_root;
}
else if(Rank[y_root] > Rank[x_root])
{
father[x_root] = y_root;
}
else
{
father[x_root] = y_root;
Rank[y_root]++;
}
}
}
void Initialization(int n)
{
for (int i = 1; i <= n; i++)//i=0也可
{
father[i] = i;
Rank[i] = 1;
}
}
最后来一句:
灯神永远滴神!
并查集2.0部分
题目链接–合并集合
并查集在解决树的不平衡问题是采用的压缩路径的办法使得树相对稳定,但是每次find_root依旧是一个比较大的时间的损耗
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int father[N],rk[N];
void Inti(int n);
bool Judge(int x, int y);
void Unite(int x, int y);
int find_root(int x);
int main()
{
int n, m;
cin >> n >> m;
char ord; int x, y;
Inti(n);
while (m--)
{
cin >> ord >> x >> y;
if (ord == 'M') Unite(x, y);
else {
if (Judge(x, y)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
bool Judge(int x, int y)
{
int x_root = find_root(x);
int y_root = find_root(y);
if (x_root==y_root) return true;
else return false;
}
void Unite(int x, int y)
{
int x_root = find_root(x);
int y_root = find_root(y);
if (x_root != y_root) {
if (rk[x_root] > rk[y_root])
{
father[y_root] = x_root;
}
else if (rk[y_root] > rk[x_root])
{
father[x_root] = y_root;
}
else {
father[x_root] = y_root;
rk[y_root]++;
}
}
}
int find_root(int x)
{
if (x == father[x]) return x;
else return find_root(father[x]);
}
void Inti(int n)
{
for (int i = 1; i <= n; i++)
{
father[i] = i;
rk[i] = 1;
}
}
在跟y总学习的过程中发现一种更好的路径压缩的办法,可以实现将并查集的时间保持在O(1)左右
之前压缩路径只是通过使树相对平衡,缩短查找该节点的祖先节点的时间,但在这一条路径上依旧有很多节点,他们的祖先节点是相同的,每一次查找都要沿着路径走,还是有点浪费时间,那如果在每一次对节点查找路径的过程中,将路径上的所有节点的祖先直接标记到根节点时间不久剩了很多吗?

#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int father[N];
int find(int x)//返回x的祖宗节点+路径压缩
{
if (x == father[x]) return x;
else return father[x] = find(father[x]);//return千万不能丢,我是傻子啊啊啊!!
//在找祖先节点的过程中更新路径中各节点的祖先节点
}
int main()
{
int n,m;
cin >> n >> m;
//inti
for (int i = 1; i <= n; i++)
father[i] = i;
char ord; int x, y;
while (m--)
{
cin >> ord >> x >> y;
if (ord == 'M')
father[find(x)] = find(y);
//边进行连接边更新路径上所有节点的祖先节点,省去了后面查找的时间
else {
if (find(x) == find(y)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}
这个代码不仅省时间,而且短
例题2–联通块中点的数量
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
int father[N],sum[N];
int find(int x)
{
if (x == father[x]) return x;
else return father[x] = find(father[x]);
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
father[i] = i;
sum[i] = 1;
}
string ord; int a, b;
while (m--)
{
cin >> ord;
if (ord == "C")//unite
{
cin >> a >> b;
if (find(a) != find(b)) {
int temp = find(a);
father[find(a)] = find(b);
sum[find(b)] += sum[temp];
}
}
else if (ord == "Q1")
{
cin >> a >> b;
if (find(a) == find(b)) cout << "Yes" << endl;
else cout << "No" << endl;
}
else
{
cin >> a;
cout << sum[find(a)] << endl;
}
}
return 0;
}
浙公网安备 33010602011771号