【算法】【并查集】路径压缩+按秩合并

介绍

并查集是一个树形的数据结构,有两种操作:

  • 查询:判断两个元素是否在同一集合
  • 合并:把两个不相交集合合并成一个集合

算法

在这里插入图片描述
如图,发现结点d和g属于同一集合,要进行合并。则

  • 查询+路径压缩(find方法):从d和g开始向上寻找根节点,并将路径上所有结点直接指向根节点。生成了右上图的树。
  • 按秩合并(union方法):将两个集合合并为一个集合,因此要将结点数小的集合的根节点的父节点设置为另一个集合的根节点,并更新集合大小,完成集合合并。

两种操作的时间复杂度都为O(h),h为树高。空间复杂度为O(n),n为节点数。

模板

力扣684 冗余连接

public class UnionSet684 {
    class Node {
        int size; // 集合结点数量
        int par;  // 集合根节点
        public Node(int par){
            this.par =par;
            this.size = 1;
        }
    }
    Node[] nodes;

    /**
     * 路径压缩
     * @param index 查找下标
     * @return 集合根节点
     */
    private int find(int index) {
        // 当前节点为根则返回根
        if (nodes[index].par == index) {
            return index;
        }
        // 递归,将当结点到根节点路径上所有的结点直接连到根节点
        nodes[index].par = find(nodes[index].par);
        // 返回集合根节点
        return nodes[index].par;
    }

    /**
     * 按秩合并xy下标结点
     * @return 合并是否成功(如果已经在一棵树里则合并失败)
     */
    private boolean union(int x,int y) {
        int px = find(x);
        int py = find(y);
        if (px == py) {
            return false;
        }
        // 集合小的合并到集合大的跟下
        if (nodes[px].size < nodes[py].size) {
            nodes[px].par = nodes[py].par;
            nodes[py].size +=nodes[px].size;
        } else {
            nodes[py].par = nodes[px].par;
            nodes[px].size +=nodes[py].size;
        }
        return true;
    }
    private void init(int[][] edges) {
        nodes = new Node[edges.length];
        for (int i = 0; i < edges.length; i++) {
            nodes[i] = new Node(i);
        }
    }
    public int[] findRedundantConnection(int[][] edges) {
        init(edges);
        for(int i = 0; i < edges.length; i++) {
            if (!union(edges[i][0]-1,edges[i][1]-1)) {
                return edges[i];
            }
        }
        return null;
    }
}

例题

力扣684 冗余连接 判断环

力扣1254 统计封闭岛屿数量

POJ2492 a bug’s life并查集扩展

17年做的题,这里作为例题

题的解答方法跟食物链差不多,可以看看食物链的解析再看这个。
食物链 http://blog.csdn.net/dreambyday/article/details/65447189
rela[x]=0表示同性
rela[x]=1表示异性

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<string.h>
#include<algorithm>
#include<time.h>
#include<cmath>
using namespace std;
#define mem(arr,a) memset(arr,a,sizeof(arr))
#define power(a) (a*a)
#define N 50010
int par[N];
int rela[N];
void init(int n){
	for (int i = 1; i <= n; i++){
		par[i] = i;
		rela[i] = 0;
	}
}
int find(int x){
	if (x == par[x])return x;
	int t = par[x];
	par[x] = find(par[x]);
	rela[x] = (rela[x] + rela[t]) % 2;
	return par[x];
}
void unite(int r1, int r2, int b, int c){
	par[r1] = r2;
	rela[r1] = (rela[b] +1+rela[c] + 2) % 2;
}
int t, n, m;
int main(){
	cin >> t;
	int cnt = 1;
	while (t--){
		cin >> n >> m;
		init(n);
		bool flag = false;
		while (m--){
			int a, b;
			scanf("%d%d", &a, &b);
			int r1 = find(a);
			int r2 = find(b);
			if (r1 != r2){
				unite(r1, r2, a, b);
			}
			else{
				if (rela[a] == rela[b]){
					flag = true;
				}
			}
		}
		if (flag){
			printf("Scenario #%d:\nSuspicious bugs found!\n", cnt++);
		}
		else
			printf("Scenario #%d:\nNo suspicious bugs found!\n", cnt++);
		printf("\n");
	}
}









  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
克鲁斯卡尔算法是一种用于求解最小生成树的贪心算法,而并查集是一种用于维护元素分组信息的数据结构。它们在解决图论问题中经常一起使用。 克鲁斯卡尔算法的基本思想是,通过不断选择边权值最小且不会产生环路的边,逐步构建最小生成树。在实现过程中,使用并查集来判断两个节点是否属于同一个连通分量,以避免形成环路。 并查集是一种用于解决集合合并与查询问题的数据结构。它通过维护一棵树来表示每个元素所属的集合,其中每个节点指向其父节点,树的根节点表示该集合的代表元素。通过路径压缩和按秩合并等优化策略,可以提高并查集的效率。 在克鲁斯卡尔算法中,首先将图中的所有边按权值从小到大排序,然后依次选择边进行判断。当选择一条边时,判断该边连接的两个节点是否属于同一个连通分量。如果不属于同一个连通分量,则选择该边,并将两个节点合并到同一个连通分量中。重复这个过程直到选择了 n-1 条边,其中 n 是图中节点的个数,即得到最小生成树。 克鲁斯卡尔算法的时间复杂度主要取决于排序边的时间复杂度,一般情况下为 O(ElogE),其中 E 是边的数量。并查集的操作时间复杂度为 O(α(n)),其中 α(n) 是一个非常慢增长的函数,可以认为是常数级别。因此,整个算法的时间复杂度为 O(ElogE)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值