前言
并查集这个结构,最适合用来解决关于连通的问题,下面介绍下并查集的思想以及附上一道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();
}
}