【并查集】Union Find

LeetCode - 547. Friend Circles

There are N students. Some of them are friends, while some are not. Their friendship is transitive in nature.
For example, if A is a friend of B, and B is a friend of C, then A is an friend of C.
And we defined a friend circle is a group of students who are friends.

Given a N*N matrix M representing the friend relationship between students in the class.
If M[i][j] = 1, then the ith and jth students are friends with each other, otherwise not.
And you have to output the total number of friend circles among all the students.

Note: N is in range [1,200]. M[i][i] = 1 for all students. If M[i][j] = 1, then M[j][i] = 1.

  就是说用一个类似邻接矩阵的方阵来表示谁和谁是朋友,核心思想就是朋友的朋友就是朋友。这很符合并查集的原则,将每一个圈子的人用一个各自 leader 来表示,然后判断两个人是不是一个圈子的,只需要判断他们 leader 是否是同一个人即可。比如把所有腾讯的员工 union 到一起,就是让他们的最大 leader 都变成 MHT,当然他们每个人可能有自己的上司,上司又有自己的上司,但是通过这个找上司的过程找下去,一定最后会找到 MHT,这样他们就是一个圈子的了,同理把阿里的员工 union 到一起,他们的最高 leader 都是 MY,那么判断两个人是不是一个圈子的,只需要判断他们的最高 leader 是不是一个人就行了。这就引发了下边几个问题:

  1. 怎么表示记录自己的上司?
    用一个数组 pre,pre[i] 记录 pre 的上司;
  2. 怎么表示 leader?
    pre[i] = i 的就是 leader,也就是最高领导;
  3. 怎么初始化 pre 数组?
    初始化所有 pre[i] = i,也就是最开始所有人是自己的最高领导;
  4. 查找 leader 的过程是否可以加速?
    可以,查找的出一个人的 leader 后,把这个人以及他所有上司,都变成 leader 的直接下属,也就是只有根节点和一层叶子节点的树;
  5. 怎么知道最后有多少个圈子?
    用一个 unordered_set 记录一共有多少个 leader。

  用代码讲,下边这个 unionFind 就是用来查找一个人的 leader 并进行路径压缩的。

// 返回n的leader, 并进行路径压缩
int unionFind(int n) {
    int leader = n;
    while(leader != pre[leader]) // 找到n的掌门: leader
        leader = pre[leader];
    // 路径压缩, 路径上所有人都变成 leader 的直接下属
    while(pre[n] != leader) {
        const int tmp = pre[n];
        pre[n] = leader;
        n = tmp;
    }
    return leader;
}

  unionUnion 用来将 a 和 b 合并成一个圈子,当 a 和 b 的 leader 不同时,随便让其中一个 leader 变成另一个 leader 的下属就可以了(这时候树可能就不是只有两层了),就好像公司被别人收购了一样= =。

// 合并a和b的圈子
void unionUnion(int a, int b) {
    const int x = unionFind(a), y = unionFind(b);
    if(x != y) 
        pre[x] = y; // 让一个成为另一个的下属
}

  这道 Friend Circles 的题就很容易写了:

vector<int> pre;

// 返回n的leader, 并进行路径压缩
int unionFind(int n) {
    int leader = n;
    while(leader != pre[leader]) // 找到n的掌门: leader
        leader = pre[leader];
    while(pre[n] != leader) {
        const int tmp = pre[n];
        pre[n] = leader;
        n = tmp;
    }
    return leader;
}

// 合并a和b的圈子
void unionUnion(int a, int b) {
    const int x = unionFind(a), y = unionFind(b);
    if(x != y) 
        pre[x] = y; // 让一个成为另一个的下属
}

int findCircleNum(vector<vector<int>>& M) {
    pre = vector<int>(201);
    // 初始化每个人为自己的 leader
    for(int i = 0; i < pre.size(); ++i)
        pre[i] = i;
    for (int i = 0; i < M.size(); ++i)
        for (int j = i + 1; j < M.size(); ++j)
            if (M[i][j])
                unionUnion(i, j);
    int cnt = 0;
    unordered_set<int> st;
    for(int i = 0; i < M.size(); ++i){
        const int leader = unionFind(i);
        if(st.find(leader) == st.end())
            st.insert(leader);
    }
    return st.size();
}

  并查集原来一行就可以完成查找和路径压缩:

int find(int x) {
	return x == f[x] ? x : f[x] = find(f[x]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值