并查集
概念
在一些应用问题中,将N个不同元素划分成一些不相交的集合 ,开始时每个元素自成一个集合 ,然后按照一定规律将同一组元素进行合并。为了方便查询不同元素是否为一个集合 (是否都与某个数据有关联 ,通常一个集合中,每个数据统一都与一个数据有关联,这个数据称为根),引入了并查集这一数据结构

原则
1. 数组下标 对应集合中元素的编号
2. 下标对应的元素大于0:表示该编号所在集合的根
3 .下标对应的值小于零:负号代表这个下标表示根 ,数字代表这个集合中有多少个元素

应用
并查集一般用于解决以下问题:
1.查找元素属于哪个集合/是否为同一集合
沿着数组一直找到根 (负数的位置),对应的下标表示这个集合中的根,若下标相同,则代表是同一集合
2.将两个集合合并为一个集合
将A集合根下标对应的元素 和B集合根下标对应的元素相加,并存储到A集合的根元素,然后将B集合的根元素改为A集合的根下标

3.集合的个数
遍历数组,计算负数元素的个数
简单实现
因为初始情况下,数组中每个元素都代表着一个集合,集合中元素的个数都为1,所以将数组每个元素都初始化为-1
构造方法
java
public int[] elem;
public MyUnionFindSet(int val) {
this.elem = new int[val];
Arrays.fill(elem,-1);
}
检查两个元素是否是同一集合
首先分别找到两个元素根 ,并判断根是否一样
java
private int findRoot(int x){
if(x<0) throw new ArrayIndexOutOfBoundsException();
while(elem[x]>=0){
x=elem[x];
}
return x;
}
public boolean isSameSet(int x1,int x2){
if(x1==x2) return true;
int r1=findRoot(x1);
int r2=findRoot(x2);
return r1==r2;
}
将两个元素所在的集合合并
首先判断这两个元素是否在同一集合,如果在同一集合,那么不需要合并
如果不在同一集合,修改集合A和集合B根下标对应的元素
java
public void union(int x1,int x2){
if(x1==x2) return;
int r1=findRoot(x1);
int r2=findRoot(x2);
if(r1==r2) return;
elem[r1]=elem[r1]+elem[r2];
elem[r2]=r1;
}
计算集合的个数
计算数组中负数元素的个数
java
public int getCount(){
int count=0;
for(int x:elem ){
if(x<0) count++;
}
return count;
}
有关并查集的oj题

依次遍历二维数组的每一行每一列,如果元素为1,那么将两个下标按照并查集规则合并
java
class Solution {
public static int[] elem;
private int findRoot(int x){
while(elem[x]>=0){
x=elem[x];
}
return x;
}
private void union(int x,int y){
if(x==y) return;
int r1=findRoot(x);
int r2=findRoot(y);
if(r1==r2) return;
elem[r1]=elem[r1]+elem[r2];
elem[r2]=r1;
}
public int findCircleNum(int[][] isConnected) {
int len=isConnected[0].length;
elem=new int[len];
for(int i=0;i<len;i++){
elem[i]=-1;
}
for(int i=0;i<len;i++){
for(int j=0;j<len;j++){
if(isConnected[i][j]==1) union(i,j);
}
}
int count=0;
for(int x:elem){
if(x<0) count++;
}
return count;
}
}
首先判断等号的情况:只要是等号,那么就按照并查集的原则进行合并
然后再次遍历,判断不等号的情况:如果是不等号,那么找到两个元素的根 ,如果根相同 ,那么返回false
两次遍历之后如果没有返回false那么最后返回true
java
class Solution {
public static int[] elem;
private int findRoot(int x){
while(elem[x]>=0){
x=elem[x];
}
return x;
}
private boolean union(int x,int y){
if(x==y) return true;
int r1=findRoot(x);
int r2=findRoot(y);
if(r1==r2) return true;
elem[r1]=elem[r1]+elem[r2];
elem[r2]=r1;
return true;
}
public boolean equationsPossible(String[] equations) {
elem=new int[27];
for(int i=0;i<27;i++){
elem[i]=-1;
}
for(String str:equations){
if(str.charAt(1)=='='){
union(str.charAt(0)-'a',str.charAt(3)-'a');
}
}
for(String str:equations){
if(str.charAt(1)=='!'){
int r1=findRoot(str.charAt(0)-'a');
int r2=findRoot(str.charAt(3)-'a');
if(r1==r2) return false;
}
}
return true;
}
}
LRUCache
LRUCache是Least Recently Used 的缩写,意思是最近最少使用(很久没使用) 。Cache在广义上指的是位于速度相差较大的两个硬件之间,用于协调两者数据传输速度差异的结构
Cache的容量有限,因此当Cache的容量用完之后,又有新的元素想要插入进来,那么就要舍弃原有的部分内容。LRUCache替换原则就是将最近最少使用的内容替换掉 ,就是很久没使用的元素
为了实现快速的查找与删除,LRUCache一般使用哈希表+双向链表来实现。
LinkedHashMap
LinkedHashMap 是JDK中类似LRUCache的数据结构
构造方法:

initialCapacity :初始容量
loadFactor 负载因子
accessOrder false:按照插入顺序排序
true:按照访问时间,动态排序 ,最近一次访问过的数据放在最后
通过哈希表和双向链表简单实现
首先定义一个静态内部类 用来实例化双向链表的节点 ,为了方便插入和删除,我们在头和尾都引入傀儡节点,所以在声明节点的内部类中,要给一个没有参数的构造方法和传 key , value 的构造方法
java
static class LinkedNode{
int val;
int key;
LinkedNode next;
LinkedNode prev;
public LinkedNode(int key,int val) {
this.val = val;
this.key=key;
}
public LinkedNode() {
}
}
我们使用库里的HashMap来实现哈希表,用来存放key和对应的双向链表节点
构造方法:
java
public MyLRUCache(int capacity) {
cache=new HashMap<>();
head=new LinkedNode();
tail=new LinkedNode();
this.capacity=capacity;
usedSize=0;
}
添加元素(put)
首先通过哈希表来检测要添加的元素key是否存在
java
LinkedNode node=cache.get(key);
已经存在
1.更新node节点上的val值
2.移除node节点
3.将node节点添加到尾部
java
private void removeHead(LinkedNode node){
node.prev.next=node.next;
node.next.prev=node.prev;
}
private void addToTail(LinkedNode node){
if(usedSize==0){
head.next=node;
node.prev=head;
node.next=tail;
tail.prev=node;
}else{
tail.prev.next=node;
node.prev=tail.prev;
node.next=tail;
tail.prev=node;
}
}
//put方法:
node.val=val;
removeHead(node);
addToTail(node);
不存在
1.定义一个node节点存放key和value
2.将key和node存放到哈希表中
3.将node添加到链表的尾部并且usedSize++
4.如果usedSize大于 设定的容量,那么移除头节点 ,并且在哈希表中删除对应的key,同时usedSize--
java
LinkedNode nodeN=new LinkedNode(key,val);
cache.put(key,nodeN);
addToTail(nodeN);
this.usedSize++;
if(usedSize>capacity){
removeHead(head.next);
cache.remove(head.next.key);
this.usedSize--;
}
获取元素(get)
通过哈希表的get方法来获取对应的node
如果node==null则不存在
如果node!=null 则返回对应的val值 ,并且将node节点移到尾部
java
public int get(int key){
LinkedNode node=cache.get(key);
if(node==null){
return -1;
}
removeHead(node);
addToTail(node);
return node.val;
}