代码随想录——并查集

并查集主要有三个功能。

  1. 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
  2. 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
  3. 判断两个节点是否在同一个集合,函数: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 {};
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值