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

介绍

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

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

算法

在这里插入图片描述
如图,发现结点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.youkuaiyun.com/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");
	}
}









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值