并查集总结

并查集

并查集是一种树型的数据结构,用于处理(不相交)集合的合并、查询及由此衍生的问题。其功能主要有三个:

  1. 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
  2. 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
  3. 判断两个节点是否在同一个集合,函数:same(int u, int v),就是判断两个节点是不是同一个根节点

实现

并查集的实现分为四个部分:

初始化

初始化时把每个节点的祖先节点设为自身,可看作创建了一个大的森林。

    int n = 1005; // 假设节点数在1005以内
    int father[1005]; // father并不代表父节点而是代表根节点

    void init() {
        for (int i = 0; i < 1005; i++)
            father[i] = i;
    }
查找祖先节点

查找节点 u 对应的祖先节点,同时保存它的祖先节点。

    int find(int u) {
        return u == father[u] ? u : father[u] = find(father[u]);
    }

使用递归查找,第一次要一直找到本身值与祖先节点值相等的节点(即根节点),因为沿途的祖先节点的值都保存下来了,所以以后对沿途的节点查找祖先节点只需要一次递归。

是否在同一个集合

通过祖先节点(根节点)是否相同判断两个节点是否在同一个集合中。

    bool same(int u, int v) {
        return find(u) == find(v);
    }
合并

把两个节点合并到一个集合上。

    void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[v] = u;
    }

先查找两个节点是否已在同一个集合上,若不是,则合并。注意,这里不能用same来判断是否在同一个集合上,因为我们除了要判断是否在同一个集合上,还要找到两个节点的根节点以合并,即合并不只是合并两个节点,而是合并这两个节点所在的集合。

例题

684.冗余连接

在这里插入图片描述

这道题就可以用并查集来解决,我们变脸edges数组,对其中每个边的两个节点将其加入同一个集合中,若发现这两个节点已经加入,则说明再加入此边就会形成环,则这一条边必不能存在,必须删除。

因为是要返回最右边需要删除的边,所以我们从左至右遍历,为什么返回最右边还要从左至右呢。因为我们是从左至右建立集合直到找到一个必须要删除的边,那后面的边可不可能是答案呢,不可能,因为我们找到的边是一定会导致环的边,即这条边一定要删除,删除后面的边而不删除这条边得到的树不满足题意,所以返回从左至右找到的第一条不满足条件的边。

代码如下:

class Solution {
private:
    int n = 1005;
    int father[1005];

    void init() {
        for (int i = 0; i < 1005; i++)
            father[i] = i;
    }

    int find(int u) {
        return u == father[u] ? u : father[u] = find(father[u]);
    }

    void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[v] = u;
    }

    bool same(int u, int v) {
        return find(u) == find(v);
    }
public:
    vector<int> findRedundantConnection(vector<vector<int>>& edges) {
        init();
        for (int i = 0; i < edges.size(); i++) {
            if (same(edges[i][0], edges[i][1])) return edges[i];
            else join(edges[i][0], edges[i][1]);
        }
        return {};
    }
};
685.冗余连接II

在这里插入图片描述

这题和冗余连接差别还是挺大的,因为冗余连接里面只有形成环的情况,而这道题还有更加复杂的情况。

往题中所说的树加一条边,要么加到根节点上,则必形成环,要么加到其他节点上,可能形成环也可能不形成环(注意这里所说的环是指有向图的环!)。我们必须先讨论加到其他节点的情况。加到其他节点,必然造成某一节点入度为2,所以我们统计入度为2的节点的两条边,注意要倒序统计,因为题目要求返回最后出现在二维数组的答案。统计完成后我们就尝试删除。删除也有两种情况,一种是删除两条边任意一条即可,这种情况我们就删靠后的;另一种情况是只有其中一条符合条件,因为删除另外一条不能消除环,判断是否有环还是用上面的方法。如果是加到根节点的情况,就会形成环,则又和冗余连接那道题相同了。代码如下:

class Solution {
private:
    static const int n = 1005;
    int father[n];

    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]);
    }

    void join(int u, int v) {
        u = find(u);
        v = find(v);
        if (u == v) return;
        father[v] = u;
    }

    bool same(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }

    bool IstreeAfterRemove(vector<vector<int>> &edges, int delete_edge) {
        init();
        for (int i = 0; i < edges.size(); ++i) {
            if (i == delete_edge) continue;
            if (same(edges[i][0], edges[i][1]))
                return false;
            join(edges[i][0], edges[i][1]);
        }
        return true;
    }

    vector<int> RemoveEdge(vector<vector<int>> &edges) {
        init();
        for (int i = 0; i < edges.size(); ++i) {
            if (same(edges[i][0], edges[i][1]))
                return edges[i];
            join(edges[i][0], edges[i][1]);
        }
        return {-1};
    }
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int degree[n] = {0};
        for (int i = 0; i <edges.size(); ++i) {
            degree[edges[i][1]]++;
        }
        vector<int> two_degree;
        for (int i = edges.size() - 1; i >= 0; --i) {
            if (degree[edges[i][1]] == 2)
                two_degree.push_back(i);
        }
        if (two_degree.size() > 0) {
            if (IstreeAfterRemove(edges, two_degree[0]))
                return edges[two_degree[0]];
            else
                return edges[two_degree[1]];
        }
        return RemoveEdge(edges);
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值