• 并查集及相关变形


    1.要解决的问题

    一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。

    现在要进行 m 个操作,操作共有两种:

    M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
    Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;

    2.并查集

    2.1支持的操作

    • 将两个集合合并
    • 询问两个数是否在同一个集合中

    2.2解决问题的方法

    将各个集合构建成一个树,树的每一个节点是在同一个集合中的数。树的编号是根节点的编号。
    为此,需要为每一个节点 i 存放其父节点 p[i]。

    2.3要思考的问题

    • 如何判断树根
      根节点的特征是其父节点是其本身
    if(p[x] == x) x就是根节点
    
    • 1
    • 如何求数x所在集合的编号
    while(x != p[x]) x = p[x];
    
    • 1

    只要x不是根节点,x就一直往上走,知道x是树根。树根的编号就是集合的编号

    • 如何将两个数所在集合合并
      p[x]是x的集合编号,p[y]是y的集合编号
    p[x] == y
    
    • 1
    • 询问两个节点是否在一个集合中:
    if(p[i] == p[j]) Yes
    else No
    
    • 1
    • 2

    2.4 优化

    树的深度很大时,每次判断x所在集合的编号都要走很多层。为此,采用“路径压缩”的方式,找到x的根节点后,将整个路径上的节点都直接指向根节点

    3.代码实现

    #include
    using namespace;
    const int N = 100010;
    // 存放每个节点的父节点
    int p[N];
    // n代表数的个数,m代表操作的个数
    int n,m;
    
    // 寻找x的根节点+路径压缩
    int find(int x){
    	if(p[x] != x) p[x] = find(p[x]);
    	return p[x];
    }
    int main(){
    	scanf("%d%d", &n, &m);
    	//初始化各个节点的父节点
    	for(int i = 0; i < n; i++){
    		p[i] = i;
    	}
    	while(m--){
    		char op[2];
    		int a, b;
    		scanf("%s%d%d",&op, &a, &b);
    		// 将两个集合进行合并
    		if(op[0] == 'M') p[find(a)] = find(b);
    		// 询问两个数是否在同一个集合中
    		else{
    			if(find(a) == find(b)) puts("Yes");
    			else puts("No");
    		}
    	}	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    4.并查集应用二-连通块中点的数量

    4.1题目描述

    给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

    现在要进行 m 个操作,操作共有三种:

    C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
    Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
    Q2 a,询问点 a 所在连通块中点的数量;

    4.2 要考虑的问题

    每个连通块还是用一个树来表示。
    前两项操作与并查集要进行的操作一致,本题需要多考虑一条连通块中点的数量。
    另设一个int count[N],代表每个集合中点的数量。因此在初始化点的父节点时,初始化每个集合中含有的点的数量。

    4.3代码实现

    #include
    using namespace std;
    
    const int N = 100010;
    
    // 存放每个节点的父节点,一共有不超过N个节点
    int p[N];
    // 存放每个集合中点的大小
    int cnt[N];
    
    // 查找每个点的父节点+路径压缩
    int find(int x){
    	if(p[x] != x) p[x] = find(p[x]);
    	return p[x];
    }
    
    int main(){
    	int n, m;
    	scanf("%d%d", &n, &m);
    	// 初始化
    	for(int i = 0; i < n; i++){
    		p[i] = i;
    		cnt[i] = 1;
    	}
    	while(m--){
    		string op;
    		int a, b;
    		cin >> op;
    		// 将两个数所在集合合并
    		if(op == "C"){
    			scanf("%d%d", &a, &b);
    			// 只有a,b不在一个连通块里才需要合并
    			if(find(a) != find(b)){
    				// 一定先合并数量再改变树的结构
    				cnt[find(b)] += cnt[find(a)];
    				p[find(a)] = find(b);
    			}
    		}
    		// 判断两个数是否在同一集合
    		else if(op == "Q1"){
    			scanf("%d%d", &a, &b);
    			if(find(a) == find(b)) printf("Yes\n");
    			else printf("No\n");
    		}
    		// 询问连通块中点的数量
    		else{
    			scanf("%d", &a);
    			printf("%d\n", cnt[find(a)];
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
  • 相关阅读:
    图解分布式事务实现原理(一)
    addIdleHandler代码不执行
    QTextStream(文本流)
    学会编程,能拿高薪?别再被洗脑了
    建表时设置主键自增
    Linux环境下安装JDK、Tomcat、MySQL并测试服务
    Vue2、Vue3知识总结---完整版✨
    工作流Activiti 迁移 Camunda
    vscode 配置 Rust 运行环境
    【TensorFlow深度学习】使用TensorBoard可视化模型训练过程与性能指标
  • 原文地址:https://blog.csdn.net/weixin_43943476/article/details/127439542