并查集的设计以及LeetCode题目:岛屿的数量

前言

并查集这个结构,最适合用来解决关于连通的问题,下面介绍下并查集的思想以及附上一道LeetCode题目

并查集的设计

先来说下并查集结构以及思想。

结构

它是有三个结构,一个是元素map,一个是儿子-父亲map,一个是rankMap,第一个map是用来包装值对应的element,第二个map对应的是每个element对应的父亲element,第三个map用来装每个父element对应的该集合的大小。我们来看看代码

public static class Element<V>{
    
    
    V value;
    public Element(V value){
    
    
        this.value = value;
    }
}
public static class UnionAndSet<V>{
    
    
    private HashMap<Element<V>,Element<V>> fatherMap;
    private HashMap<V,Element<V>> elementMap;
    private HashMap<Element<V>,Integer> sizeMap;
    public UnionAndSet(List<V> list){
    
    
        this.fatherMap = new HashMap<>();
        this.elementMap = new HashMap<>();
        this.sizeMap = new HashMap<>();
        //初始化集合,每个元素都是一个集合
        for (V v : list) {
    
    
            Element<V> element = new Element<>(v);
            elementMap.put(v,element);
            fatherMap.put(element,element);
            sizeMap.put(element,1);
        }
    }
}

实现

然后再来看下它的两个最重要的方法,union(v1,v2)和isSameSet(v1,v2);
union的思想就是:将两个元素对应的头结点找出来,如果两个元素对应的头结点不相同,接下来我们就需要将他们两个集合进行连通,更新fatherMap和sizeMap
isSameSet:它的思想和实现都非常的简单,只需要找出两个元素的头结点,判断两个头结点是否相等就行了,相等就返回true,否则返回false;
我们来看看整体的代码:

public static class Element<V>{
    
    
    V value;
    public Element(V value){
    
    
        this.value = value;
    }
}
public static class UnionAndSet<V>{
    
    
    private HashMap<Element<V>,Element<V>> fatherMap;
    private HashMap<V,Element<V>> elementMap;
    private HashMap<Element<V>,Integer> sizeMap;
    public UnionAndSet(List<V> list){
    
    
        this.fatherMap = new HashMap<>();
        this.elementMap = new HashMap<>();
        this.sizeMap = new HashMap<>();
        //初始化集合,每个元素都是一个集合
        for (V v : list) {
    
    
            Element<V> element = new Element<>(v);
            elementMap.put(v,element);
            fatherMap.put(element,element);
            sizeMap.put(element,1);
        }
    }
    //得到两个元素所属集合的头结点
    private Element<V> getHeader(Element<V> element){
    
    
        Stack<Element<V>> stack = new Stack<>();
        Element<V> father = fatherMap.get(element);
        while(element != father){
    
    
            stack.push(element);//沿途所有节点入栈
            element = father;
            father = fatherMap.get(element);
        }
        //退出循环表示找到头结点了,这时候需要做的事情是将沿途所有节点都指向这个父节点
        while(!stack.isEmpty()){
    
    
            fatherMap.put(stack.pop(),father);
        }
        return father;
    }
    //合并两个节点
    public void union(V v1,V v2){
    
    
        //合并的前提需要两个元素都存在elementMap里
        if (elementMap.containsKey(v1) && elementMap.containsKey(v2)){
    
    
            Element<V> father_v1 = getHeader(elementMap.get(v1));
            Element<V> father_v2 = getHeader(elementMap.get(v2));
            if (father_v1 == father_v2){
    
    
                return ;
            }
            //只有在头节点不同的情况下才进行连通
            Element<V> big = father_v1;
            Element<V> small = father_v2;
            int size_v1 = sizeMap.get(big);
            int size_v2 = sizeMap.get(small);
            big = size_v1 < size_v2 ? small : big;
            small = big==father_v1?father_v2:father_v1;
            int size = sizeMap.get(big)+sizeMap.get(small);
            fatherMap.put(small,big);
            sizeMap.put(big,size);
        }
    }
    public boolean isSameSet(V v1,V v2){
    
    
        if (elementMap.containsKey(v1) && elementMap.containsKey(v2)){
    
    
            Element<V> header_v1 = getHeader(elementMap.get(v1));
            Element<V> header_v2 = getHeader(elementMap.get(v2));
            return header_v1 == header_v2;
        }
        return false;
    }
}

public static void main(String[] args) {
    
    
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    UnionAndSet<Integer> unionAndSet = new UnionAndSet<>(list);
    unionAndSet.union(1,2);
    unionAndSet.union(3,4);
    System.out.println(unionAndSet.isSameSet(1,3));
    unionAndSet.union(2,3);
    System.out.println(unionAndSet.isSameSet(1,3));
}

测试结果:
在这里插入图片描述

练习题

传送门

题目

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。

示例

示例 1:

输入:
[
[‘1’,‘1’,‘1’,‘1’,‘0’],
[‘1’,‘1’,‘0’,‘1’,‘0’],
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘0’,‘0’,‘0’,‘0’,‘0’]
]
输出: 1
示例 2:

输入:
[
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘0’,‘0’,‘1’,‘0’,‘0’],
[‘0’,‘0’,‘0’,‘1’,‘1’]
]
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。

思想

它的思想与并查集的思想如出一辙,我就不细解释了,只是在如何连通方面说两句:我们遍历这个二维数组,然后将每个位置封装成一个 i_j 的值进行封装,封装成每一个元素,然后在遍历的过程中,找到位置是1的就进行连通。剩下的就不多说了,看代码吧

实现

class Solution {
    
    
    public static class Element<V>{
    
    
        public V value;
        public Element(V v){
    
    
            value = v;
        }
    }
    public static class UnionAndSet<V>{
    
    
        public HashMap<V,Element<V>> elementMap;
        public HashMap<Element<V>,Element<V>> fatherMap;
        public HashMap<Element<V>,Integer> sizeMap;

        public UnionAndSet(List<V> list){
    
    
            elementMap = new HashMap<>();
            fatherMap = new HashMap<>();
            sizeMap = new HashMap<>();
            for(V v: list){
    
    
                Element<V> element = new Element<V>(v);
                elementMap.put(v,element);
                fatherMap.put(element,element);
                sizeMap.put(element,1);
            }
        }
        public Element<V> getHeader(V v){
    
    
            Element<V> element = elementMap.get(v);
            Stack<Element> stack = new Stack<>();
            while(element != fatherMap.get(element)){
    
    
                Element father = fatherMap.get(element);
                stack.push(element);
                element = father;
            }
            while(!stack.isEmpty()){
    
    
                fatherMap.put(stack.pop(),element);
            }
            return element;
        }
        public void union(V v1,V v2){
    
    
            if(elementMap.containsKey(v1) && elementMap.containsKey(v2)){
    
    
                Element father_v1 = getHeader(v1);
                Element father_v2 = getHeader(v2);
                if(father_v1 != father_v2){
    
    
                    int v1_len = sizeMap.get(father_v1);
                    int v2_len = sizeMap.get(father_v2);
                    Element<V> big = v1_len>v2_len?father_v1:father_v2;
                    Element<V> small = big==father_v1?father_v2:father_v1;
                    fatherMap.put(small,big);
                    sizeMap.put(big,v1_len+v2_len);
                    sizeMap.remove(small);
                }
            }
        }
    }
    public int numIslands(char[][] grid) {
    
    
        if(grid == null || grid.length==0 || grid[0].length==0){
    
    
            return 0;
        }
        List<String> list = new ArrayList<>();
        for(int i=0;i<grid.length;i++){
    
    
            for(int j=0;j<grid[i].length;j++){
    
    
                if(grid[i][j]=='1'){
    
    
                    String position = String.valueOf(i)+"_"+String.valueOf(j);
                    list.add(position);
                }
            }
        }
        UnionAndSet<String> unionAndSet = new UnionAndSet<>(list);
        for(int i=0;i<grid.length;i++){
    
    
            for(int j=0;j<grid[i].length;j++){
    
    
                if(grid[i][j]!='1'){
    
    
                    continue;
                }
                String position = String.valueOf(i)+"_"+String.valueOf(j);
                if(j-1>=0 && grid[i][j-1]=='1'){
    
    
                    String up = String.valueOf(i)+"_"+String.valueOf(j-1);
                    unionAndSet.union(up,position);
                }
                if(i-1>=0 && grid[i-1][j]=='1'){
    
    
                    String left = String.valueOf(i-1)+"_"+String.valueOf(j);
                    unionAndSet.union(left,position);
                }
            }
        }
        return unionAndSet.sizeMap.size();
    }
}

猜你喜欢

转载自blog.csdn.net/MarkusZhang/article/details/108014420