并查集
概述
详细教程参考之前转载的并查集详解
性质
并查集算法(union_find sets)不支持分割一个集合,求连通子图、求最小生成树
用法
并查集是由一个数组pre[]
,和两个函数构成的,一个函数为find()
函数,用于寻找前导点的,第二个函数是join()
用于合并路线的
int find(int x)
{
int r=x;
while(pre[r]!=r)
r=pre[r];//找到他的前导结点
int i=x,j;
while(i!=r)//路径压缩算法
{
j=pre[i];//记录x的前导结点
pre[i]=r;//将i的前导结点设置为r根节点
i=j;
}
return r;
}
路径压缩为了加快查找的速度,将x点与其根节点直接相连,构造成类似于只有叶子结点而没有分支结点的树
join()函数
void join(int x,int y)
{
int a=find(x);//x的根节点为a
int b=find(y);//y的根节点为b
if(a!=b)//如果a,b不是相同的根节点,则说明ab不是连通的
{
pre[a]=b;//我们将ab相连 将a的前导结点设置为b
}
}
初始化
我们将每一个结点的前导结点设置为自己,如果在join函数时未能形成连通,将独立成点
for(int i=0;i<n;i++)//n表示输入的结点的个数
{
pre[i]=i;//将每一个结点的前导点设置为自己
}
用法
试题来自第八届蓝桥杯试题
第三次编辑这道题目
标题:风险度量
X星系的的防卫体系包含 n 个空间站。这 n 个空间站间有 m 条通信链路,构成通信网。
两个空间站间可能直接通信,也可能通过其它空间站中转。
对于两个站点x和y (x != y), 如果能找到一个站点z,使得:
当z被破坏后,x和y无法通信,则称z为关于x,y的关键站点。
显然,对于给定的两个站点,关于它们的关键点的个数越多,通信风险越大。
你的任务是:已知网络结构,求两站点之间的通信风险度,即:它们之间的关键点的个数。
输入数据第一行包含2个整数n(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,链路数。
空间站的编号从1到n。通信链路用其两端的站点编号表示。
接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条链路。
最后1行,两个数u,v,代表被询问通信风险度的两个站点。
输出:一个整数,如果询问的两点不连通则输出-1.
例如:
用户输入:
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
应该输出:
2
我的错误
在进行分析的时候,我考虑了去边去点,去点的话,首先逐个去掉除了询问的点以外的点,同时去点的同时我们同样需要去掉与该点之间关联的边,查找与该点关联的边需要从整个数据中寻找数据量太大,一定会超时,还是需要考虑去边的办法,
//并查集
#include<iostream>
using namespace std;
int pre[1005];//每个点的前导点
int route[2005][2];
//可以配对的路线
int sum = 0;
//符合条件的 即关键点的数量
//查找
int find(int x)
{
int r = x;
while (pre[r] != r)
r = pre[r];
int i = x, j;
while (i != r)//路径压缩算法
{
j = pre[i];//在改变他的前导点时,存储他的值
pre[i] = r;
i = j;//改变他的前导点为根节点
}
return r;
}
void join(int x, int y)
//组合
{
int fx = find(x), fy = find(y);//分别记录x,y的根节点
if (fx != fy)//如果他们的根节点相同,则说明他们不是连通图
pre[fx] = fy;//将x的根结点 同 相连接
}
int main()
{
int n, m;
cin >> n>>m;//n表示站点的个数,m表示链路的个数
for (int i = 0; i < m; i++)
{
cin >> route[i][0] >> route[i][1];
join(route[i][0], route[i][1]);//将数据相互连接
}
int q1,q2;//待询问的两个点
cin >> q1 >> q2;
for (int ii = 0; ii < n; ii++)pre[ii] = ii;
for (int j = 0; j < m; j++)
{
join(route[j][0], route[j][1]);
}
int a = find(q1);
int b = find(q2);
//如果边全部存在时不可达,则输出 -1;
if (a != b)
{
cout << "-1" << endl;
}
else
{
for (int i = 1; i <= n; i++)
//枚举每一个点
{
if (i == q1 || i == q2)continue;
//如果是被询问的点,跳过,无需遍历 此处是最关键的部分
for (int j = 1; j <= n; j++)pre[j] = j;
//将每一个初始化
for (int j = 0; j < m; j++)
{
if (route[j][0] == i || route[j][1]==i)continue;
//去除当前点互相关联的边 解决问题的关键
int a = find(route[j][0]);
int b = find(route[j][1]);
if (a > b) { a ^= b; b ^= a; a ^= b; };//交换
if (a != b)pre[b] = a;
//以较小的点作为父节点
}
int a = find(q1);
int b = find(q2);
if (a != b)sum++;
}
cout<<sum<<endl;
}
return 0;
}
看到网上好多人在写并查集时,使用while(~sacnf("%d",&a))
scanf()函数的返回值是正确获得变量的个数
~scanf()函数就是没有得到正确的输入,总体上讲如果有正确结果输入,就退出循环,如果没有正确输入,就执行循环
看似没有什么区别,其实这种while()循环更加安全,保证不会因为非法的数字的输入执行程序的使用
测试数据
按照我之前对于数据的统计,现在给大家提供几组数据
- 4 0
1 2
测试结果 -1
- 4 3
2 3
3 4
2 4
1 4
测试结果 -1 - 3 2
1 2
2 3
1 3
测试结果 1 - 使用题目的数据 以及评论区的那个数据
- 4 3
测试数据的分析,对于第一组测试数据,有且仅有四个点 ,测试程序是否会进行连通性判断;第二组数据 1 是独立点 234是三个连通分量,程序需要判断是否1与其他的点能构成连通图;第三组数据,我们测试一条直线,所有的关键点全部在该直线上,判断程序记录的到底是关键点的个数还是边的个数;第四组我们测试任意情况下对于数据的处理我们可以打开画板,对我们的数据进行验证即可
反向并查集
题目
来自蓝桥杯系统历届试题库中的试题
问题描述
C国由n个小岛组成,为了方便小岛之间联络,C国在小岛间建立了m座大桥,每座大桥连接两座小岛。两个小岛间可能存在多座桥连接。然而,由于海水冲刷,有一些大桥面临着不能使用的危险。
如果两个小岛间的所有大桥都不能使用,则这两座小岛就不能直接到达了。然而,只要这两座小岛的居民能通过其他的桥或者其他的小岛互相到达,他们就会安然无事。但是,如果前一天两个小岛之间还有方法可以到达,后一天却不能到达了,居民们就会一起抗议。
现在C国的国王已经知道了每座桥能使用的天数,超过这个天数就不能使用了。现在他想知道居民们会有多少天进行抗议。
输入格式
输入的第一行包含两个整数n, m,分别表示小岛的个数和桥的数量。
接下来m行,每行三个整数a, b, t,分别表示该座桥连接a号和b号两个小岛,能使用t天。小岛的编号从1开始递增。
输出格式
输出一个整数,表示居民们会抗议的天数。
样例输入
4 4
1 2 2
1 3 2
2 3 1
3 4 3
样例输出
2
样例说明
第一天后2和3之间的桥不能使用,不影响。
第二天后1和2之间,以及1和3之间的桥不能使用,居民们会抗议。
第三天后3和4之间的桥不能使用,居民们会抗议。
数据规模和约定
对于30%的数据,1<=n<=20,1<=m<=100;
对于50%的数据,1<=n<=500,1<=m<=10000;
对于100%的0<=n<=10000,1<=m<=10000。
,1<=a, b<=n, 1<=t<=100000。
#include<iostream>
#include<algorithm>
using namespace std;
struct node
{
int x, y, d;//d表示剩余的时间 x,y分别表示桥的两端的端点
}bridge[10005];//创建桥的数量
int pre[10005];//前导结点的个数
bool cmp(node a, node b)//时间比较 如果第一个参数大于第二个参数,则返回true
{
return a.d>b.d;
}
int find(int x)//查找根节点
{
int r=x;
while (pre[r] != r)
r = pre[r];
//尝试尝试路径压缩算法
return r;
}
bool join(int x, int y)//合并链路
{
int fx = find(x);
int fy = find(y);
if (fx != fy)
{
pre[fx] = fy;
return 1;//没有桥,我们将直接构造形成桥
}
return 0;//有桥无需构造
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
cin >> bridge[i].x >> bridge[i].y >> bridge[i].d;//输入桥头两侧 使用天数
}
//
for (int i = 0; i < n; i++)
{
pre[i] = i;//初始化每个小岛,使其独立
}
//按照使用时间排序进行整合
sort(bridge, bridge + m, cmp);//天数从大到小排列
int fight = 0;//表示反抗的日子
int pre = -1;//
for (int i = 0; i < m; i++)//此时的时间已经是从大到小的排序
{
int way = join(bridge[i].x, bridge[i].y);//从时间从大到小重新构造桥
if (way == 1 && bridge[i].d != pre)//如果系统构造的桥并且天数不等于-1
{
fight++;
pre = bridge[i].d;
}
}
cout << fight << endl;
return 0;
}
这是我的代码在通过系统的时候由于超出限制时间,只有40%的分数,其中由于一次需要输入三个变量,并且我们在之后的操作中需要对时间进行排序,所以我们采取结构体命名变量我们建时间按照从大到小的顺序排列,将每个岛屿全部独立分开重新构建来连通图,我们将按照时间天数优先,针对测试样例,以及之前的顺序优先准则来重新构建,从第一条边开始,如果不是连通图,就呼叫一次fight++
,直到所有的结点全部构成连通图,结束执行,无论后面还有多少条未加入的边
未完待续