查并集基础

来源:互联网 发布:如何找到淘宝套现商家 编辑:程序博客网 时间:2024/06/04 20:04

对于今天要总结的算法,我想先通过一道题目来看一下:

假设现在我有一个任务交给你:要求你查看 id 为 x 和 id 为 y 的两个人是不是朋友,在一开始我会在第一行中输入 3 个数字 n、m 、k。n 是代表总人数。接下来 m 行,每一行我会输入两个数字: Xi 、 Yi, 代表 id 为 Xi 和 id 为 Yi 的两个人是朋友(注意:朋友的朋友也是朋友), 接下来 k 行,每一行我也会输入两个数字: a 和 b ,代表我要你查询 id 为 a 和 id 为 b 的两个人是不是朋友, 如果这两个人是朋友,那么在一行中输出“yes”否则在一行中输出“no”。 数据约束:0 < n, m, k < 10000, 所有人的 id 都是正整数,并且 id 不会超过 n 样例输入:7 5 41 3 2 43 41 45 61 42 32 56 7样例输出:yesyesnono

在上面的题目中,如果没有说“朋友的朋友也是朋友”这句话,那么就好办了,我们直接用一个二维数组来记录每一组朋友的信息,然后进行筛选就行了。但是有了这句话,我们就不能简单的用二维数组来解决了。那么怎么解决呢?

首先,我们可以这样想,我们先把所有的人看成独立的群体,也就是说每个人的朋友只有他自己,那么这样的话一开始就有 n 个朋友圈,之后当题目数据输入的时候我们将输入的 id 所代表的的两个人所在的两个朋友圈合并成一个大的朋友圈,那么在这个合并之后的朋友圈中,所有的人两两都是朋友(朋友的朋友也是朋友),不断重复上面的合并过程,直到题目中给的 m 行的朋友对数据全部都合并完成。之后要判断两个人是不是朋友只需要判断他们是不是在同一个朋友圈里面就可以了。

我们用题目中给出的数据来模拟这个过程,先看代码:

#include <iostream>using namespace std;const int N = 10010;int f[N];/* * 将表示朋友圈的数组初始化,即将所有人的“朋友祖先”都设置为自己的 id , * 于是就有了 n 个不同的朋友圈  */void init(int n) {    for(int i = 1; i <= n; i++) {        f[i] = i;    }}// 得到 id 为 v 的人的“朋友祖先”的 id int getFriend(int v) {      if(f[v] == v) {        return v;    }    /*     * 如果发现“朋友祖先”不是自己,那么他肯定被合并到别的朋友圈里面去了,     * 那么继续调用这个函数来找这个朋友圈里面的“朋友祖先”,     * 并且在这个过程中将找到的人都设置为同一个“朋友祖先”(因为都在同一个朋友圈里面)      */    return f[v] = getFriend(f[v]);}// 将两个人所在的两个朋友圈合并为一个朋友圈 void merge(int a, int b) {    int t1 = getFriend(a); // 得到左边的人的“朋友祖先”     int t2 = getFriend(b); // 得到右边的人的“朋友祖先”     /* 这里我们制定一个“靠左原则”:一旦发现两个人的“朋友祖先”不一样,     * 那么右边那个人的“朋友祖先”的“朋友祖先”设置为左边的人的“朋友祖先”,     * 当然,也可以制定“靠右原则”      */     if(t1 != t2) {          f[t2] = t1;    }}int main() {    int n, m, k;    cin >> n >> m >> k;    int x, y;    init(n);    for(int i = 0; i < m; i++) {        cin >> x >> y;        merge(x, y);    }     for(int i = 0; i < k; i++) {        cin >> x >> y;        // 如果两个人的“朋友祖先”不相同,证明他们不在同一个朋友圈内,也就不是朋友        if(getFriend(x) != getFriend(y)) {            cout << "no" << endl;         } else {            cout << "yes" << endl;        }    }    return 0;} 

用图来模拟这一过程:

这里写图片描述

如果不能理解可以把例题数据带进去代码中自己模拟一遍就知道了,最后我们来看一下运行结果:

这里写图片描述

我们可以加一段代码来输出合并之后的数组情况:

#include <iostream>using namespace std;const int N = 10010;int f[N];/* * 将表示朋友圈的数组初始化,即将所有人的“朋友祖先”都设置为自己的 id , * 于是就有了 n 个不同的朋友圈  */void init(int n) {    for(int i = 1; i <= n; i++) {        f[i] = i;    }}// 得到 id 为 v 的人的“朋友祖先”的 id int getFriend(int v) {      if(f[v] == v) {        return v;    }    /*     * 如果发现“朋友祖先”不是自己,那么他肯定被合并到别的朋友圈里面去了,     * 那么继续调用这个函数来找这个朋友圈里面的“朋友祖先”,     * 并且在这个过程中将找到的人都设置为同一个“朋友祖先”(因为都在同一个朋友圈里面)      */    return f[v] = getFriend(f[v]);}// 将两个人所在的两个朋友圈合并为一个朋友圈 void merge(int a, int b) {    int t1 = getFriend(a); // 得到左边的人的“朋友祖先”     int t2 = getFriend(b); // 得到右边的人的“朋友祖先”     /* 这里我们制定一个“靠左原则”:一旦发现两个人的“朋友祖先”不一样,     * 那么右边那个人的“朋友祖先”的“朋友祖先”设置为左边的人的“朋友祖先”,     * 当然,也可以制定“靠右原则”      */     if(t1 != t2) {          f[t2] = t1;    }}int main() {    int n, m, k;    cin >> n >> m >> k;    int x, y;    init(n);    for(int i = 0; i < m; i++) {        cin >> x >> y;        merge(x, y);    }    /*     * 输出合并之后的数组情况     */    for(int i = 1; i <= n; i++) {        if(i != 1) {            cout << " ";        }        cout << f[i];    }    cout << endl;    for(int i = 0; i < k; i++) {        cin >> x >> y;        // 如果两个人的“朋友祖先”不相同,证明他们不在同一个朋友圈内,也就不是朋友        if(getFriend(x) != getFriend(y)) {            cout << "no" << endl;         } else {            cout << "yes" << endl;        }    }    return 0;} 

结果:

这里写图片描述

和我们在纸上模拟的结果一样,一共有三个朋友圈。
这个时候当数组某个位置的值等于其所在下标的时候,id 等于这个值的人就是这个朋友圈的“朋友祖先”, 有多少个“朋友祖先”就有多少个朋友圈

Ok,其实上面说的这种算法思想就是查并集,其标准的描述也是通过树和森林来定义的:在一个森林中有很多棵不同的树,我们通过一些信息来将一些不同的分开的树合并成一棵大的树。在这道题目中,一开始森林中有 7 棵不同根节点的树,树的根节点在这道题目中就相当于“朋友祖先”(7 个朋友圈,每个朋友圈中只有一个人,即为他自己,也是每个朋友圈的“朋友祖先”),通过题中所给的信息不断合并朋友圈(合并森林中不同的树),合并结束之后森林中树的棵树或者不同的树的根节点的个数(“朋友祖先”的个数)就是朋友圈的个数。

好了,查并集的基本思想就总结到这里了,有兴趣的小伙伴可以去试试这道题,上面的例题也是通过这道题简化得来的,题目链接:https://www.patest.cn/contests/gplt/L2-010

如果博客中有什么不正确的地方,还请多多指点。如果觉得我写的不错,那么请点个赞支持我吧。

谢谢观看。。。

0 0
原创粉丝点击