一)并查集
在一些应用问题中,需要将N个不同的元素划分成一些互不相交的集合,开始的时候,每一个元素自成一个单元素集合,然后按照一定的规律将归于同一组元素的集合进行合并,并且在此过程中需要反复使用到查询某一个元素是属于哪一个集合,适合于描述这种抽象问题的数据类型称之为是并查集
1)假设某公司今年校招全国一共招生10人,西安招4人,成都招三人,武汉招四人,10个人来自于不同的学校,起初这10个人之间相互都不认识,每一个学生都是一个独立的小团体,现在给这些学生进行编号,{0,1,2,3,4,5,6,7,8,9},给以下数组用于存储小集体
2)毕业以后发现,学生们要去公司上班,每一个地方的学生自发的组织小分队开始上路,于是西安学生小分队{0,6,7,8},武汉学生小分队{2,3,5},成都学生小分队{1,4,9},每一个小分队的人的第一位同学当队长,带领大家找工作,这样的原来每一个地区互相不认识的几个人现在都相互认识了
4)从上图可以看出:编号6,7,8同学属于0号小分队,该小分队中有4人(包含队长0),编号为4和9的同学属于1号小分队,该小分队有3人(包含队长1),编号为3和5的同学属于2号小分队,该小分队有3个人(包含队长1)
5)从这里可以总结:
5.1)数组的下标代表集合中元素的编号
5.2)数组中下标的数如果代表的是负数,那么数组的下标的编号就是队长,此时这个负号代表这个下标就是此树形关系中的根节点,数字代表以当前下标为根节点的这棵二叉树中有多少元素
5.3)数组中如果某一个下标的数代表是非负数那么这个非负数代表的是这个下标的父亲节点
6)假设此时在公司工作一段时间后,西安小分队中8号同学与成都小分队1号同学奇迹般的走到了一起,两个小圈子的学生相互介绍,最后成为了一个小圈子
7)现在0集合中有7个人,2集合中有3个人,一共有两个朋友圈,通过上述例子可知,并查集可以解决下面的几个问题:
7.1)查找元素属于哪一个集合:沿着数组表示的属性关系以上找到根,根节点就是数组下标中元素是负数的位置
7.2)查看两个元素是否属于同一个集合:沿着数组表示的属性关系向上一直找到树的根,如果跟相同则表明是在同一个集合中,否则不在同一个集合中
7.3)将两个集合合并成1个集合:可以将两个集合中的元素进行合并,将一个集合中的名称改成另一个集合的名称
7.4)集合的个数:遍历整个数组,数组中元素是负数的个数就是集合中元素的个数
合并两个集合:
1)当合并两个数的时候, 这两个数必须来自于不同的集合,就拿上面的6和7来进行举例,这两个数本身都在同一个集合中了,因此对于他们的合并是没有任何意义的,所以如果给定两个数,如果他们的根节点相同,那么就不需要进行合并,所以合并一定是两个不同的集合中的元素;
2)以上面的这种图进行举例,假设要合并4和8所在的元素集合,首先找到根节点,分别是index1和index2
2.1)array[index1]=array[index1]+array[index2],这里面是更新新的根节点中的元素个数,更新孩子的数量
2.2)array[index2]=index1,更新另一棵树的根节点
public class UnionFindSet {public int[] array;public int usedSize;public UnionFindSet(int n){//n代表数组长度this.array=new int[n];Arrays.fill(array,-1);}public boolean isUnionSet(int x,int y){//查询x和y是否在同一个集合,就需要判断x和y的根节点是否相同int father1=findRoot(x);int father2=findRoot(y);return father1==father2;}public int findRoot(int data){//查找数据x的根节点if(data<0) throw new ArrayIndexOutOfBoundsException("此时data不能不是负数");while(array[data]>=0){data=array[data];}return data;//此时返回的是根节点,而不是根节点对应的数字,此时下标已经代表的是根节点的数值了}//合并两个集合public void union(int x,int y){int father1=findRoot(x);int father2=findRoot(y);if(father1==father2){System.out.println("两个元素是属于同一个集合中的不需要进行合并");return;}array[father1]=array[father1]+array[father2];//选取father1来充当合并以后的根节点3array[father2]=father1;//更新被合并的那个数的根节点}//查找集合的个数public int GetCount(){int count=0;for(int num:array){if(num<0) count++;}return count;}public static void main(String[] args) {//根据上面这个合并的代码要重点理解合并的过程:UnionFindSet set=new UnionFindSet(100);//1.先合并0,6,7,8set.union(0,6);set.union(7,8);set.union(0,7);//2.在合并1 4 9set.union(1,4);set.union(4,9);//3.最后合并2 3 5set.union(2,3);set.union(3,5);//4.观察最终结果System.out.println(Arrays.toString(set.array));set.union(8,1); //set.union(1,8)得到的就是index2也就是8充当根节点System.out.println(Arrays.toString(set.array));System.out.println(set.isUnionSet(6,9));//也可以判断他们是否是同一个亲戚} }
并查集——亲戚(洛谷 P1551)_并查集试炼之亲戚_是一只派大鑫的博客-CSDN博客
并查集的应用:
1)省份数量:
547. 省份数量 - 力扣(LeetCode)
class UnionFindSet {public int[] array;public int usedSize;public UnionFindSet(int n){//n代表数组长度this.array=new int[n];Arrays.fill(array,-1);}public boolean isUnionSet(int x,int y){//查询x和y是否在同一个集合,就需要判断x和y的根节点是否相同int father1=findRoot(x);int father2=findRoot(y);return father1==father2;}public int findRoot(int data){//查找数据x的根节点if(data<0) throw new ArrayIndexOutOfBoundsException("此时data不能不是负数");while(array[data]>=0){data=array[data];}return data;//此时返回的是根节点,而不是根节点对应的数字,此时下标已经代表的是根节点的数值了}//合并两个集合public void union(int x,int y){int father1=findRoot(x);int father2=findRoot(y);if(father1==father2){System.out.println("两个元素是属于同一个集合中的不需要进行合并");return;}array[father1]=array[father1]+array[father2];//选取father1来充当合并以后的根节点3array[father2]=father1;//更新被合并的那个数的根节点}//查找集合的个数public int GetCount(){int count=0;for(int num:array){if(num<0) count++;}return count;} } class Solution {public int findCircleNum(int[][] array) {UnionFindSet set=new UnionFindSet(array.length);for(int i=0;i<array.length;i++){for(int j=0;j<array[0].length;j++){if(array[i][j]==1){set.union(i,j);}}}return set.GetCount();} }
2)等式方程的可满足性
990. 等式方程的可满足性 - 力扣(LeetCode)
1)根据题目解析可以看到,如果发现字符串的第二个位置是等于号,那么代表这两个位置的字符是相等的,就可以合并,如果是!那么代表这两个字符不是相等的,就不能合并
2)算法原理:
2.1)如果发现str.charAt(1)=='='那么说明这个字符串主要做的就是一个合并操作,那么只需要把str.charAt(0)和str.charAt(3)对应的字符进行合并即可
2.2)如果在发现遍历整个字符串的过程中,str.charAt(1)=="!",说明此时这个字符串的功能执行的不是一个将两个字符串的合并操作,而是一个检查操作,此时就需要检查str.charAt(0)和str.charAt(3)是否合并过,如果是合并过的,直接返回false,否则直接返回true;
class UnionFindSet {public int[] array;public int usedSize;public UnionFindSet(int n){//n代表数组长度this.array=new int[n];Arrays.fill(array,-1);}public boolean isUnionSet(int x,int y){//查询x和y是否在同一个集合,就需要判断x和y的根节点是否相同int father1=findRoot(x);int father2=findRoot(y);return father1==father2;}public int findRoot(int data){//查找数据x的根节点if(data<0) throw new ArrayIndexOutOfBoundsException("此时data不能不是负数");while(array[data]>=0){data=array[data];}return data;//此时返回的是根节点,而不是根节点对应的数字,此时下标已经代表的是根节点的数值了}//合并两个集合public void union(int x,int y){int father1=findRoot(x);int father2=findRoot(y);if(father1==father2){System.out.println("两个元素是属于同一个集合中的不需要进行合并");return;}array[father1]=array[father1]+array[father2];//选取father1来充当合并以后的根节点3array[father2]=father1;//更新被合并的那个数的根节点}//查找集合的个数public int GetCount(){int count=0;for(int num:array){if(num<0) count++;}return count;} } class Solution {public boolean equationsPossible(String[] strings) {UnionFindSet set=new UnionFindSet(26);for(String str:strings){if(str.charAt(1)=='='){set.union(str.charAt(0)-'a',str.charAt(3)-'a');//防止数组越界}}for(String str:strings){if(str.charAt(1)=='!'){int father1=set.findRoot(str.charAt(0)-'a');int father2=set.findRoot(str.charAt(3)-'a');if(father1==father2) return false;}}return true;} }
二)LRUCache
linkedHashMap双向链表+哈希表,会根据你插入的顺序来输出最终的结果