并查集主要有三个功能。
- 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
- 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
- 判断两个节点是否在同一个集合,函数:same(int u, int v),就是判断两个节点是不是同一个根节点
以下模板汇总,只要修改 n 和father数组的大小即可
int n = 1005; // 节点数量3 到 1000
int father[1005];
// 并查集初始化
void init() {
for (int i = 0; i < n; ++i) {
father[i] = i;
}
}
// 并查集里寻根的过程
int find(int u) {
return u == father[u] ? u : father[u] = find(father[u]);
}
// 将v->u 这条边加入并查集
void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return ;
father[v] = u;
}
// 判断 u 和 v是否找到同一个根
bool same(int u, int v) {
u = find(u);
v = find(v);
return u == v;
}
更多并查集概念参考:并查集——知乎
1.冗余连接
解题关键:将每条边的两个节点都加入到同样一个集合中,如果出现某一条边的两个节点已经是在同一个集合中,说明这两个节点已经加入过集合了,再次加入一定会成环。
class Solution {
vector<int> father;
public:
void init()
{
//初始化每一个节点的根节点是自己
for(int i=0;i<father.size();i++)
{
father[i]=i;
}
}
//寻找节点i的根节点
int find(int child)
{
//如果节点child节点等于其根节点,则直接返回
if(father[child]==child)
return father[child];
//如果节点child节点不等于其根节点,则递归判断child根节点的根节点即为child的根节点
else
father[child]=find(father[child]);
return father[child];
}
//将节点a所在的集合与节点b所在的集合合并
void together(int a,int b)
{
//首先判断节点a的根节点是否等于节b的根节点,如果相等,则已经是在一个集合了
if(find(a)==find(b))
return;
//如果是,则将节点b根节点作为节点a的根节点
father[find(a)]=find(b);
}
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
//father[i]代表节点i的父节点,因此该数组长度取决于题目中元素的最大值
father=vector<int>(1200);
init();
//将每条边的两个节点都加入到同样一个集合中,如果出现某一条边的两个节点已经是在同一个集合中,说明这两个节点已经加入过集合了,再次加入一定会成环
for(int i=0;i<edges.size();i++)
{
if(find(edges[i][0])!=find(edges[i][1]))
together(edges[i][0],edges[i][1]);
else
return edges[i];
}
return {};
}
};
2.冗余连接||
首先要明确一点,那就是找到加一条边使树成环的情况,有如下两种情况:
情况一:成环处,节点间首尾依次相连成环,每个节点单进单出。针对该情况,只要按照正常的并查集去重方法去除最后一条重复进入的【节点对】即可。
情况二:环中存在某一个节点入度为2。针对该情况,需要删除的边必然在上述两条边之中,但是无法确认删除哪一条。所以首先需要将两条边同时找出来,删除其中一条,再判断剩下的边组成的是否为成环的树。如果不是,则要删除的边即为上述两条中的另一条。【注意】由于要删除的是最靠右的边,所以要优先判断这两条边中后一条是否满足结果。
class Solution {
vector<int> root;
public:
//初始化每个节点所属的根节点即为自身
void init()
{
for(int i=0;i<root.size();i++)
root[i]=i;
}
//寻找节点cur的根节点
int find(int cur)
{
if(root[cur]==cur)
return root[cur];
root[cur]=find(root[cur]);
return root[cur];
}
//将节点a所在集合与节点b所在集合合并
void together(int a,int b)
{
if(root[a]==root[b])
return;
root[b]=find(root[a]);
}
vector<vector<int>> array;//用于存放找到度为2的节点对应的两条边
vector<int> du;//用于存放每个节点的入度
void searchLine(vector<vector<int>>& edges)
{
int node=0;//记录入度为2的节点
for(int i=0;i<edges.size();i++)
{
du[edges[i][1]]++;
//当节点的入度为2时
if(du[edges[i][1]]==2)
{
node=edges[i][1];
}
}
//找到入度为2的两条边,并存入数组array
for(int j=0;j<edges.size();j++)
{
//找到以入度为2的节点关联的两条边
if(edges[j][1]==node)
array.push_back(edges[j]);
}
}
//判断删除一条边line后树是否成环
bool isCircle(vector<vector<int>>& edges,vector<int>line)
{
for(int i=0;i<edges.size();i++)
{
//跳过要删除的边
if(edges[i]==line)
continue;
//使用并查集查找,如果两个节点重复加入并查集,则必然成环
if(find(edges[i][0])==find(edges[i][1]))
return false;
together(edges[i][0],edges[i][1]);
}
return true;
}
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
root=vector<int>(1200);
du=vector<int>(1200,0);
init();
//判断情况二
searchLine(edges);
//如果存在入度为2的节点,这要删除的边必在这两条边
if(array.size()!=0)
{
//【注意】由于要删除的是最靠右的边,所以要优先判断array[1]是否满足结果
if(isCircle(edges,array[1]))
return array[1];
else
return array[0];
}
//判断情况一:
//如果没有入度为2的节点,这按照正常并查集来求解重复的边
//【注意】需要初始化每个节点的根节点数组
init();
for(int i=0;i<edges.size();i++)
{
if(find(edges[i][0])==find(edges[i][1]))
return edges[i];
together(edges[i][0],edges[i][1]);
}
return {};
}
};