题目一
力扣:684
思路 :并查集
并查集
1、一定心中要有数,一旦涉及了二者是否所属同一个集合,尤其在图论里面,一定要想到并查集,并查集可以以很快速的速度查询该元素所属集合,以及合并集合,有时候我们可以根据两个元素是否属于同一个集合来做文章解题,也可以根据集合的个数来解题(即记录集合个数,当集合不断合并只剩下一个时候的情况等等问题。)
2、并查集很好实现分成两块介绍
存储
1、并查集维护的是一些元素所属的集合,为了更快速的知道集合是谁,所以做出来一个主元素,他就指向他自己。
2、并查集内部每一个集合都按照树的数据结构存储,每一个元素存的都是它上一级元素
3、初始的时候,每个元素就是单独自己一个集合,因为没有输入任何有关的关系。
功能
1、find()。能够找到每个元素所在集合的代表元,因为在并查集中,每个元素存的都是他在树上的上一个元素,所以按照这个路径查找上去就行了,直到发现自己指向自己。
2、find()函数特别的,要进行压缩查询,对路径上的节点要都一起指向集合的代表元,这样子会更提高查询速度。
3、merge函数,就是将两个集合合并,只要把任意一个集合的代表元连到另外的集合就行
本题思路
读题,在一个树的基础上,再加一笔,一颗树上的节点都属于一个集合,当读入一条边的时候,如果边的两个点同时属于一个树的时候,这个边就是要找的冗余边,上文也说了,一颗树上的节点都属于一个集合,所以就要看两个点属不属于一个集合,这个点与集合从属关系,用来维护点和集合关系的东西,一看到就应该立刻想到并查集。
代码
class unionFindSet {
//封装的并查集类
public:
//初始化
unionFindSet(int n) {
this->n = n;
settings.resize(n + 1);
for (int i = 0; i <= n; i++) {
settings[i] = i;
}
}
//find操作
int find(int item) {
if (item <= 0 || item > n) {
//防止越界
return 0;
}
if (settings[item] == item) {
//找到代表元,即自己指向自己
return item;
}
settings[item] = find(settings[item]);//压缩查询,让已知路径上所有点都指向代表元,加速查询
//将代表元赋予路径上所有节点
return settings[item];
}
//merge操作
void merge(int x, int y) {
settings[find(y)] = find(x);//两个集合代表顶点融合
}
private:
vector<int> settings;//维护的并查集
int n;//该并查集大小
};
class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size();
unionFindSet uf_set(n);
int i = 0;
for (; i < n; i++) {
int x = edges[i][0];
int y = edges[i][1];
if (uf_set.find(x) != uf_set.find(y)) {
//如果两个点还不在一个集合,说明再构造树,这个边合法
uf_set.merge(x, y);
}
else {
//反之,说明这两个边已经在一个集合了,即在一棵树上了,再加边就是符合题意的冗余边
break;
}
}
return edges[i];
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
附加题目
网络灾难
字节跳动在全国各地都建立了服务器集群,各个服务器之间通过光缆连接形成了网状拓扑结构。
有一天邪恶的外星人入侵了集群,他们想破坏字节跳动的网络。
他们会残忍地切断一条一条网线,直到所有光缆都被切断。
现在给出这个拓扑结构以及外星人切断光缆的顺序,求当外星人切到第几根光缆时,字节跳动的网络会完全断裂成多块。
断裂成多块的定义为:当某一次光缆被切断后,若服务器A和服务器B完全没有光缆直接或间接连接,则称整个网络结构断裂成多块。
输入描述
第一行有两个数n m 表示字节跳动的服务器集群个数,以及光缆的数目。
接下来m行 第1+x行 有两个数 a,b 表示服务器集群a 和 服务器集群b有光缆相连,该光缆编号为x。
接下来还有m行 第1+m+x行 有一个数c,表示外星人在第x步会切断编号为c的光缆。
保证初始网络没有分裂。
输出描述
只有一个数 ans,表示外星人在第ans步切断光缆之后,字节跳动的网络会分裂成多个块。
示例1
输入
4 5
1 2
1 3
1 4
2 3
2 4
1
2
3
4
5
输出
3
说明
当1-2、1-3、1-4的光缆被切断后,服务器集群1 和 其他服务器集群完全断裂。
思路
这个题非常的经典,我们这里提供思路和未经检验的代码(代码没经过OJ,可能有错误)
1、我们正向思考,在一堆乱如麻的线路中,每剪掉一根,都得看看是不是连通的,很复杂。
2、一定要有逆向思维,当你发现正向很难走通的时候,我们不妨反过来思考,外星人的目的是什么,按照顺序把所有的线路剪掉,反过来呢,按照逆序连上所有的线路,所以我们可以按照外星人剪线路的逆序,生成图,当某一条线被连上时候,整个图被连通,那么这根线就是外星人在剪线路时候,决定图是否连通的关键。
3、这个题就涉及到了一直加边什么时候才能连通,一张连通图,所有的点必定都是在一个大的集合里,最开始图里面只有点,每个点各自一个集合,随着边的增加,每加一条边,就会少一个集合,这样,到最后答案边加进来的时候,恰好一个集合。
4、综上,涉及到了集合和集合的关系,以及随着边的加入,边就代表着两个点是等价的、在一起的,集合逐渐减少,所以用并查集维护各个集合,最开始多少个点多少个集合,随着边的加入,集合逐渐合并,直到最后一条边使得集合数=1为止。
示例代码(可能有错误,参照思路即可)
//C
#include<stdio.h>
#include<malloc.h>
typedef struct _Dsu{
int * father;
int size;
}Dsu;
Dsu * createDsu(int size){
//创建并查集
Dsu * dsu = (Dsu*)malloc(sizeof(Dsu));
dsu->size = size;
dsu->father = (int*)malloc(sizeof(int) * (size + 1));
for(int i = 1;i <= size;i++){
dsu->father[i] = i;
}
return dsu;
}
int find(Dsu * dsu,int k){
if(k > dsu->size || k <= 0){
return 0;
}
if(dsu->father[k] == k){
return k;
}
dsu->father[k] = find(dsu,dsu->father[k]);
return dsu->father[k];
}
void merge(Dsu * dsu,int x,int y){
dsu->father[find(x)]] = find(y);
}
int edge[100050][2];//边集合
int idx[100050];
int main(){
int n,m;
scanf("%d%d",&n,&m); //读入nm
for(int i = 0;i < m;i++){
//读入所有边
scanf("%d%d",&edge[i][0],&edge[i][1]);
}
for(int i = 1;i <= m;i++){
//读入删边次序
scanf("%d",&idx[i]);
}
Dsu * dsu = createDsu(n);
int cnt = n;
for(int i = m,x,y;i > 0;i--){
//逆序加边
x = edge[idx[i]][0];
y = edge[idx[i]][1];
if(find(dsu,x) != find(dsu,y)){
//如果新边两定点不在同一集合,则合并,等价类-1
merge(dsu,x,y);
cnt--;
}
if(cnt == 1){
//如果等价类数量唯一,得到答案i
printf("%d",i);
break;
}
}
}