从0开始学算法--基础数据结构(2.8并查集)

算法理解:

  根据名字就能很好的理解这个算法,集合合并查询

合并什么?查询什么?

  合并操作为:把x所在的集合和y所在的集合合并为一个集合。查询x和y是否在一个集合里。

如:元素为1-n,这n个元素分别在编号为1-n的集合中。如果将3和5合并成为一个集合,只需要将元素3指向元素5即可

现在把元素5和元素2所在的集合合并合并,5指向2

可以看到如果要查询2和3是不是在一个集合中,只需要查询2和3的祖先是不是同一个元素就可以了

查询祖先代码

//f[x]初始化为f[i]=i;
int find_set(int x){
    while(x!=f[x])x=f[x];
    return x;
}

优化一:可以看到合并集合时本质是形成了一颗集合树,用树顶的元素代表这个集合,既然是树那么时间复杂度就和树的深度有关系,在合并时尽可能的让树的深度小一些就可以减少时间复杂度,这种方法叫做按秩合并

void unite(int a,int b{
    int x=find_set(a);
    int y=find_set(b);
    if(x==y)return;//x和y在一个集合中,不用合并
    if(rank[x]<rank[y]){//rank为树深
        f[x]=y;
    }else{
        f[y]=x;
        if(rank[x]==rank[y]){
            rank[x]++;
        }
    }
}

优化二:在查询祖先时,我们访问了树上祖先到这个节点的所有节点,那么我们让这些节点的所有节点都直接指向祖先节点就可以大大的减少时间复杂度。

int find_set(int x){
    return f[x]==x?x:f[x]=find_set(f[x]);
}

例题:题目链接 poj2524

  当今世界上有许多不同的宗教,要了解它们是很困难的。你想知道你们学校的学生信仰多少种不同的宗教。你知道你们学校有n个学生(0 <n(= 50000))。你不可能问每个学生的宗教信仰。此外,许多学生对表达他们的信仰感到不自在。避免这些问题的一种方法是问m (0 <=m (= n(n-1)/2)对学生,问他们是否信仰相同的宗教(例如,他们可能知道他们是否参加同一个教堂)。从这些数据中,你可能不知道每个人都信仰什么,但你可以了解校园里可能代表多少种不同宗教的上限。你可能认为每个学生最多只信奉一种宗教。

  输入输入由许多情况组成。下面的m行分别由两个整数i和j组成,指定学生i和j信仰同一宗教。学生们编号为1至n.输入端由n=m=0的行指定。

  输出对于每个测试用例,在一行中打印案例编号(从1开始),然后是该大学学生信仰的不同宗教的最大数量。

Sample Input

10 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 10
10 4
2 3
4 5
4 8
5 8
0 0

Sample Output

Case 1: 1
Case 2: 7
#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;
int disjoint_set[50010];
int a[50010];
int findset(int x){
    return disjoint_set[x]!=x?disjoint_set[x]=findset(disjoint_set[x]):x;
}

int readint(){
    int x;scanf("%d",&x);return x;
}

int main(){
    int n,m;
    int ss=0;
    while(~scanf("%d%d",&n,&m)){
            ss++;
        if(n==0&&m==0)break;
        for(int i=1;i<=n;i++){
            disjoint_set[i]=i;
            a[i]=0;
        }
        for(int i=0,v,c;i<m;i++){
            scanf("%d%d",&v,&c);
            int x=findset(v);
            int y=findset(c);
            if(x!=y){
                disjoint_set[x]=y;
            }
        }
        for(int i=1;i<=n;i++){
            a[findset(i)]=1;
        }
        int sum=0;
        for(int i=1;i<=n;i++){
            if(a[i]==1)sum++;
        }
        printf("Case %d: ",ss);
        printf("%d\n",sum);
    }
    return 0;
}

注释:优化一与优化二各有优劣,优化1的优点在于每次只改变了f[]数组的一个值,优化二的优点在于时间复杂度较低

原文地址:https://www.cnblogs.com/wz-archer/p/11713984.html

时间: 2024-11-08 21:50:57

从0开始学算法--基础数据结构(2.8并查集)的相关文章

cdoj 1328 卿学姐与诡异村庄 Label:并查集 || 二分图染色

卿学姐与诡异村庄 Time Limit: 4500/1500MS (Java/Others)     Memory Limit: 65535/65535KB (Java/Others) Submit Status 日复一日,年复一年,春去秋来. 卿学姐终于从天行廖那里毕业啦.出山的卿学姐首先来到了一个诡异的村庄. 在这个村庄中,只有两种人,一种是好人,一种是坏人. 好人只说真话,坏人只说假话. 村庄虚伪的平静由于卿学姐的到来,终于被打破了. 人们开始互相指控,每个人都会说另外一个人是否是好人.

算法(第四版)之并查集(union-find算法)

开个新坑, 准备学习算法(第四版), 并把上面学到的东西写成博客, 毕竟以前也学过一点算法, 但效果甚微 并查集, 在这本书的第一章1.5中叫做union-find算法, 但在其他地方这个叫做并查集,就是说一系列点的连通问题,比如, 我们有十个点, 分别记作0~9: 加入我们要把2和4连接起来怎么表示呢? 首先我们会想到,给所有的点标上一个号, 来代表他们的连通关系, 我们初始化这个数就是他们id本身: 如果我们要连接2和4, 就使得4的id为2: 之后要连接间隔点任意两个点, 就把它们和它们相

【算法学习笔记】41.并查集 SJTU OJ 1283 Mixture

---恢复内容开始--- Description CC非常喜欢化学,并且特别喜欢把一大堆液体倒在一起. 现在CC有n种液体,其中m对会发生反应,现在她想把这n种液体按某种顺序倒入一个容器内,让她获得最刺激的体验,使危险系数尽量大. 我们可以这样计算危险系数,一开始容器内没有任何液体,危险系数为1.每次液体倒入容器时,若容器内已有一种或多种液体会与这种液体发生反应,则危险系数会乘2,否则危险系数不变. 请你求出把这n种液体倒在一起的最大危险系数. Input Format 第一行为两个数n和m.

算法复习(1)——并查集

翻翻我做过的286道题,发现忘了好多不记得啥了  呵呵呵.... 于是毅然决定老师让好好复习复习.. 第一节---并查集 1.what is 并查集?? 并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其 间要反复查找一个元素在哪个集合中.这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往 在空间上过大,计算机无法承受:即使在空间上勉

算法-图是否为树(并查集或深搜)

今天做了一道很有意思的一道题,这道题虽然难度只是中等,但是里面涉及到的东西却是不少.其中,我在里面学习到了并查集这个东西,虽然不是很深刻,至少有一个印象:还有深搜,一直以来,深搜和广搜都是我的弱项,本文的理解是基于别人的博客:lintcode178. graph valid tree 图是否是树.我们来看看题 题意: 给出 n 个节点,标号分别从 0 到 n - 1 并且给出一个 无向 边的列表 (给出每 条边的两个顶点), 写一个函数去判断这张`无向`图是否是一棵树 样例: 给出n = 5 并

【算法学习笔记】44. 并查集补充 SJTU OJ 3015 露子的星空

[题目描述] 已经深夜了,露子仍然在公园里仰望星空.你走近后,她对你说:“呜—,你看夜空中的星星.它们本来都是孤独地毫无联系,但人们赋予了它们各种地联想,在它们之间连上了线,便形成了夜空中无数的星座.”你回答:“是啊.为什么我们不自己创造一个美丽的星空呢?” 假设夜空中一共有n颗星星,它们初始时都没有连线.接下来,露子会给你m条指令.一共有以下三种指令: 1.在某两颗星星间连线.(可能会重复连接两颗星星) 2.询问你当前夜空中一共有多少个星座. 3.某两颗星星当前是否属于同一个星座. 其中星座是

从0开始学算法--数据结构(2.4双端队列与单调队列)

双端队列是特殊的队列,它与队列不同的是可以将元素加入头或尾,可以从头或尾取出元素(滑稽-这部就是栈和队列结合了吗). c++标准库 头文件 #include<deque> 定义 deque<int>deq; 取出队头,尾元素 deq.pop_front(); deq.pop_back(); 访问队头,尾元素 deq.front(); deq.back(); 向队头,尾加入元素 deq.push_front(x); deq.push_back(x); 单调队列是在队列的基础上使它保持

从0开始学算法--数据结构(2.3队列)

顾名思义:队列就像排序一样,先排队的人先得到处理 队列与栈类似:队列是一个先进先出表 首先考虑数组模拟,如果线性数组模拟,会导致占用空间过多,为什么?数组模拟栈会遇到这样的问题吗? 因为队列是一个先进先出表,比如加入5个元素占用的是数组下表的0-4号位置,这时候删除两个元素,0-1号位置.为了维护这个队列的结构就会导致0-1号位置占用但不利用. 栈取出的是数组尾部的元素,所以不会占用额外的空间. 优化:“将数组变为环形”(模的意义). 如果我们每次加入元素是给数组下表取模,那么整个数组就变成了环

从0开始学算法--排序(1.5归并排序)

算法理解: 一个数组长度为n,他的前m个元素是升序的,后n-m个元素升序的,怎么使整个数组变成一个升序数组? 如n=6,m=3 1 3 5 2 4 6 1 2 3 4 5 6 排序前 排序后 #include <algorithm> #include <iostream> #include <cstring> #include <vector> #include <cstdio> #include <cmath> #include &