并查集(Union-find)及经典问题

并查集(Union-find)及经典问题

解决的是连通性问题

并查集是一种抽象画很高的数据结构, 常用于描述集合,经常用于解决此类问题:某个元素是否属于某个集合,或者 某个元素 和 另一个元素是否同属于一个集合

Union-find实现

public class UnionFind {
    int[] nums;//保存各个节点的父节点---其实就是该节点的前一个链接节点
    int[] size;//保存节点下子节点的数量---权重
    int count;//还剩多少个节点未连通
    public UnionFind(int n){//初始化
        this.nums = new int[n];
        this.size = new int[n];
        this.count = n;
        for(int i = 0;i < n;i++){
            this.nums[i] = i;//每个节点的父节点都是其本身
            this.size[i] = 1;//每个节点的权重都是1
        }
    }
    //寻找一个节点的顶层父节点
    public int find(int a){
        int num = a;
        while(nums[num] != num){//当他的父节点不是他
            nums[num] = nums[nums[num]];//这一步是为了压缩路径,画图就可以很好地理解了。
            num = nums[num];//走向该节点的父节点,继续找顶层的父节点
        }
        return num;
    }
    //判断两个节点是否连通
    public boolean union(int a,int b){
        return find(a) == find(b);//两个节点的父节点一致,就说明已经连通
    }
    //连通两个节点
    public void merge(int a,int b){
        int num_a = find(a);
        int num_b = find(b);
        if(num_a == num_b) return;
        count--;
        //判断谁的权重小,谁就放在下面
        if(size[num_a] > size[num_b]){
            nums[num_b] = num_a;
            size[num_a] += size[num_b];
        }else{
            nums[num_a] = num_b;
            size[num_b] += size[num_a];
        }
    }
    public int getCount(){
        return this.count;
    }
}

测验:

public class Main{
    public static void main(String args[]){
        UnionFind unionFind = new UnionFind(10);
        unionFind.merge(0,1);
        unionFind.merge(1,2);
        System.out.println("0->2:" + (unionFind.union(0,2) ? "已连通" : "未连通"));
        unionFind.merge(3,4);
        unionFind.merge(4,7);
        System.out.println("3->7:" + (unionFind.union(3,7) ? "已连通" : "未连通"));
        System.out.println("1->7:" + (unionFind.union(1,7) ? "已连通" : "未连通"));
    }
}
0->2:已连通
3->7:已连通
1->7:未连通

基础题目

LeetCode547. 省份数量

class Solution {
    public int findCircleNum(int[][] isConnected) {
        int n = isConnected.length;
        UnionFind unionFind = new UnionFind(n);//需要粘过来UnionFind的代码,太长了所以这里就不粘贴了
        for(int i = 0;i < n;i++){
            for(int j = 0;j < i;j++){//i < j是因为,该矩阵是对阵矩阵,避免重复计算
                if(isConnected[i][j] == 1) unionFind.merge(i,j);
            }
        }
        int res = 0;
        for(int i = 0;i < n;i++){
            if(unionFind.find(i) == i) res++;
        }
        return res;
    }
}

LeetCode200. 岛屿数量

当然这道题用并查集不一定是最好的方法

class Solution {
    public int numIslands(char[][] grid) {
        int n = grid.length;
        int m = grid[0].length;
        UnionFind unionFind = new UnionFind(n * m);//需要粘过来UnionFind的代码,太长了所以这里就不粘贴了
        for(int i = 0;i < n;i++){
            for(int j = 0;j < m;j++){
                if(grid[i][j] == '0') continue;
                if(i - 1 >= 0 && grid[i - 1][j] == '1') unionFind.merge(i * m + j,(i - 1) * m + j);
                if(j - 1 >= 0 && grid[i][j - 1] == '1') unionFind.merge(i * m + j,i * m + j - 1);
            }
        }
        int ans = 0;
        for(int i = 0; i < n;i++){
            for(int j = 0;j < m;j++){
                if(grid[i][j] == '1' && unionFind.find(i * m + j) == i * m + j) ans += 1;
            }
        }
        return ans;
    }
}

LeetCode990. 等式方程的可满足性

class Solution {
    public boolean equationsPossible(String[] equations) {
        UnionFind uf = new UnionFind(26);
        for(String eq:equations){
            if(eq.charAt(1) == '='){
                char x = eq.charAt(0);
                char y = eq.charAt(3);
                uf.union(x - 'a',y - 'a');
            }
        }
        for(String eq:equations){
            if(eq.charAt(1) == '!'){
                char x = eq.charAt(0);
                char y = eq.charAt(3);
                if(uf.connect(x - 'a',y - 'a')){
                    return false;
                }
            }
        }
        return true;
    }
}

LeetCode684. 冗余连接

class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        UnionFind unionFind = new UnionFind(edges.length + 1);
        int[] num =  new int[2];
        for(int[] n : edges){
            int a = n[0];
            int b = n[1];
            if(unionFind.find(a) == unionFind.find(b)) return n;
            unionFind.merge(a,b);
        }
        return num;
    }
}
//下面是并查集代码
class UnionFind {
    int[] nums;//保存各个节点的父节点---其实就是该节点的前一个链接节点
    int[] size;//保存节点下子节点的数量---权重
    int count;//还剩多少个节点未连通
    public UnionFind(int n){//初始化
        this.nums = new int[n];
        this.size = new int[n];
        this.count = n;
        for(int i = 0;i < n;i++){
            this.nums[i] = i;//每个节点的父节点都是其本身
            this.size[i] = 1;//每个节点的权重都是1
        }
    }
    //寻找一个节点的顶层父节点
    public int find(int a){
        int num = a;
        while(nums[num] != num){//当他的父节点不是他
            nums[num] = nums[nums[num]];//这一步是为了压缩路径,画图就可以很好地理解了。
            num = nums[num];//走向该节点的父节点,继续找顶层的父节点
        }
        return num;
    }
    //判断两个节点是否连通
    public boolean union(int a,int b){
        return find(a) == find(b);//两个节点的父节点一致,就说明已经连通
    }
    //连通两个节点
    public void merge(int a,int b){
        int num_a = find(a);
        int num_b = find(b);
        if(num_a == num_b) return;
        count--;
        //判断谁的权重小,谁就放在下面
        if(size[num_a] > size[num_b]){
            nums[num_b] = num_a;
            size[num_a] += size[num_b];
        }else{
            nums[num_a] = num_b;
            size[num_b] += size[num_a];
        }
    }
    public int getCount(){
        return this.count;
    }
}

LeetCode1319. 连通网络的操作次数

class Solution {
    //有n个独立的集合,需要的操作数量是n - 1次
    //如果电缆的数量小于n - 1。那么电缆的数量不够,返回-1
    public int makeConnected(int n, int[][] connections) {
        if(connections.length < n - 1) return -1;
        UnionFind unionFind = new UnionFind(n);
        for(int[] connection : connections){
            int a = connection[0];
            int b = connection[1];
            unionFind.merge(a,b);
        }
        int ans = 0;
        for(int i = 0;i < n;i++){
            if(unionFind.find(i) == i) ans += 1;
        }
        return ans - 1;
    }
}

//下面是并查集代码
class UnionFind {
    int[] nums;//保存各个节点的父节点---其实就是该节点的前一个链接节点
    int[] size;//保存节点下子节点的数量---权重
    int count;//还剩多少个节点未连通
    public UnionFind(int n){//初始化
        this.nums = new int[n];
        this.size = new int[n];
        this.count = n;
        for(int i = 0;i < n;i++){
            this.nums[i] = i;//每个节点的父节点都是其本身
            this.size[i] = 1;//每个节点的权重都是1
        }
    }
    //寻找一个节点的顶层父节点
    public int find(int a){
        int num = a;
        while(nums[num] != num){//当他的父节点不是他
            nums[num] = nums[nums[num]];//这一步是为了压缩路径,画图就可以很好地理解了。
            num = nums[num];//走向该节点的父节点,继续找顶层的父节点
        }
        return num;
    }
    //判断两个节点是否连通
    public boolean union(int a,int b){
        return find(a) == find(b);//两个节点的父节点一致,就说明已经连通
    }
    //连通两个节点
    public void merge(int a,int b){
        int num_a = find(a);
        int num_b = find(b);
        if(num_a == num_b) return;
        count--;
        //判断谁的权重小,谁就放在下面
        if(size[num_a] > size[num_b]){
            nums[num_b] = num_a;
            size[num_a] += size[num_b];
        }else{
            nums[num_a] = num_b;
            size[num_b] += size[num_a];
        }
    }
    public int getCount(){
        return this.count;
    }
}

​ LeetCode128. 最长连续序列

class Solution {
    public int longestConsecutive(int[] nums) {
        HashMap<Integer,Integer> map = new HashMap<>();
        UnionFind unionFind = new UnionFind(nums.length);
        for(int i = 0;i < nums.length;i++){
            int x = nums[i];
            if(map.containsKey(x)) continue;
            if(map.containsKey(x - 1)) unionFind.merge(i,map.get(x - 1));
            if(map.containsKey(x + 1)) unionFind.merge(i,map.get(x + 1));
            map.put(x,i);
        }
        int ans = 0;
        for(int i = 0;i < nums.length;i++){
            if(unionFind.find(i) == i && unionFind.size[i] > ans) ans = unionFind.size[i];
        }
        return ans;
    }
}

//下面是并查集代码
class UnionFind {
    int[] nums;//保存各个节点的父节点---其实就是该节点的前一个链接节点
    int[] size;//保存节点下子节点的数量---权重
    int count;//还剩多少个节点未连通
    public UnionFind(int n){//初始化
        this.nums = new int[n];
        this.size = new int[n];
        this.count = n;
        for(int i = 0;i < n;i++){
            this.nums[i] = i;//每个节点的父节点都是其本身
            this.size[i] = 1;//每个节点的权重都是1
        }
    }
    //寻找一个节点的顶层父节点
    public int find(int a){
        int num = a;
        while(nums[num] != num){//当他的父节点不是他
            nums[num] = nums[nums[num]];//这一步是为了压缩路径,画图就可以很好地理解了。
            num = nums[num];//走向该节点的父节点,继续找顶层的父节点
        }
        return num;
    }
    //判断两个节点是否连通
    public boolean union(int a,int b){
        return find(a) == find(b);//两个节点的父节点一致,就说明已经连通
    }
    //连通两个节点
    public void merge(int a,int b){
        int num_a = find(a);
        int num_b = find(b);
        if(num_a == num_b) return;
        count--;
        //判断谁的权重小,谁就放在下面
        if(size[num_a] > size[num_b]){
            nums[num_b] = num_a;
            size[num_a] += size[num_b];
        }else{
            nums[num_a] = num_b;
            size[num_b] += size[num_a];
        }
    }
    public int getCount(){
        return this.count;
    }
}
LeetCode947. 移除最多的同行或同列石头
class Solution {
    public int removeStones(int[][] stones) {
        int n = stones.length;
        UnionFind unionFind = new UnionFind(n);
        HashMap<Integer,Integer> x_index = new HashMap<>();
        HashMap<Integer,Integer> y_index = new HashMap<>();
        for(int i = 0;i < n;i++){
            int x = stones[i][0];
            int y = stones[i][1];
            if(x_index.containsKey(x)) unionFind.merge(i,x_index.get(x));
            if(y_index.containsKey(y)) unionFind.merge(i,y_index.get(y));
            x_index.put(x,i);
            y_index.put(y,i);
        }
        int ans = 0;
        for(int i = 0;i < n;i++){
            if(unionFind.find(i) == i) ans += 1;
        }
        return n - ans;
    }
}

//下面是并查集代码
class UnionFind {
    int[] nums;//保存各个节点的父节点---其实就是该节点的前一个链接节点
    int[] size;//保存节点下子节点的数量---权重
    int count;//还剩多少个节点未连通
    public UnionFind(int n){//初始化
        this.nums = new int[n];
        this.size = new int[n];
        this.count = n;
        for(int i = 0;i < n;i++){
            this.nums[i] = i;//每个节点的父节点都是其本身
            this.size[i] = 1;//每个节点的权重都是1
        }
    }
    //寻找一个节点的顶层父节点
    public int find(int a){
        int num = a;
        while(nums[num] != num){//当他的父节点不是他
            nums[num] = nums[nums[num]];//这一步是为了压缩路径,画图就可以很好地理解了。
            num = nums[num];//走向该节点的父节点,继续找顶层的父节点
        }
        return num;
    }
    //判断两个节点是否连通
    public boolean union(int a,int b){
        return find(a) == find(b);//两个节点的父节点一致,就说明已经连通
    }
    //连通两个节点
    public void merge(int a,int b){
        int num_a = find(a);
        int num_b = find(b);
        if(num_a == num_b) return;
        count--;
        //判断谁的权重小,谁就放在下面
        if(size[num_a] > size[num_b]){
            nums[num_b] = num_a;
            size[num_a] += size[num_b];
        }else{
            nums[num_a] = num_b;
            size[num_b] += size[num_a];
        }
    }
    public int getCount(){
        return this.count;
    }
}

LeetCode1202. 交换字符串中的元素

class Solution {

    public String smallestStringWithSwaps(String s, List<List<Integer>> pairs) {
        if (pairs.size() == 0) {
            return s;
        }
        // 第 1 步:将任意交换的结点对输入并查集
        int len = s.length();
        UnionFind unionFind = new UnionFind(len);
        for (List<Integer> pair : pairs) {
            int index1 = pair.get(0);
            int index2 = pair.get(1);
            unionFind.merge(index1, index2);
        }
        // 第 2 步:构建映射关系
        char[] charArray = s.toCharArray();
        // key:连通分量的代表元,value:同一个连通分量的字符集合(保存在一个优先队列中)
        Map<Integer, PriorityQueue<Character>> hashMap = new HashMap<>(len);
        for (int i = 0; i < len; i++) {
            int root = unionFind.find(i);
//            if (hashMap.containsKey(root)) {
//                hashMap.get(root).offer(charArray[i]);
//            } else {
//                PriorityQueue<Character> minHeap = new PriorityQueue<>();
//                minHeap.offer(charArray[i]);
//                hashMap.put(root, minHeap);
//            }
            // 上面六行代码等价于下面一行代码,JDK 1.8 以及以后支持下面的写法
            hashMap.computeIfAbsent(root, key -> new PriorityQueue<>()).offer(charArray[i]);
        }

        // 第 3 步:重组字符串
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < len; i++) {
            int root = unionFind.find(i);
            stringBuilder.append(hashMap.get(root).poll());
        }
        return stringBuilder.toString();
    }
}


//下面是并查集代码
class UnionFind {
    int[] nums;//保存各个节点的父节点---其实就是该节点的前一个链接节点
    int[] size;//保存节点下子节点的数量---权重
    int count;//还剩多少个节点未连通
    public UnionFind(int n){//初始化
        this.nums = new int[n];
        this.size = new int[n];
        this.count = n;
        for(int i = 0;i < n;i++){
            this.nums[i] = i;//每个节点的父节点都是其本身
            this.size[i] = 1;//每个节点的权重都是1
        }
    }
    //寻找一个节点的顶层父节点
    public int find(int a){
        int num = a;
        while(nums[num] != num){//当他的父节点不是他
            nums[num] = nums[nums[num]];//这一步是为了压缩路径,画图就可以很好地理解了。
            num = nums[num];//走向该节点的父节点,继续找顶层的父节点
        }
        return num;
    }
    //判断两个节点是否连通
    public boolean union(int a,int b){
        return find(a) == find(b);//两个节点的父节点一致,就说明已经连通
    }
    //连通两个节点
    public void merge(int a,int b){
        int num_a = find(a);
        int num_b = find(b);
        if(num_a == num_b) return;
        count--;
        //判断谁的权重小,谁就放在下面
        if(size[num_a] > size[num_b]){
            nums[num_b] = num_a;
            size[num_a] += size[num_b];
        }else{
            nums[num_a] = num_b;
            size[num_b] += size[num_a];
        }
    }
    public int getCount(){
        return this.count;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值