回复
基于双向链表和哈希表(开放地址)实现LRU缓存
blueice
发布于 2020-9-25 10:35
浏览
0收藏
一、设计思路
1. 数据存储
数据存储使用开放地址哈希表,而不是使用链表哈希的方式,从而保证存放最多指定容量的数据。如果发生冲突,则往下查找直到找到一个空位置。
对应的数据结构层面,则是使用数组,更确切地说是环形数组,因为当发生冲突时,需要查找除了直接通过 key % 容量capacity 取模之外的所有其他位置一遍,直到找到一个可以存放该键值对的位置。对于数据获取,也是类似的思路。
2. LRU特性实现
仿照Java的linkedhashmap来维护一个双向链表,每次访问一个节点或者新增一个节点则放到链表尾部,删除节点,则删除链表首部节点,实现O(1)复杂度。
二、编码实现
leetcode已通过: LRU缓存
class LRUCache {
// 解题思路:
// 仿照Java的linkedhashmap来维护一个双向链表,每次访问一个节点或者新增一个节点则放到链表尾部
// 删除节点,则删除链表首部节点
// 数据存储使用开放地址哈希表,就冲突节点放在下一个空位置,而不是使用链表哈希的方式
private Node head;
private Node tail;
private int capacity;
private int size;
private Node[] table;
public LRUCache(int capacity) {
if (capacity > 0) {
this.capacity = capacity;
this.table = new Node[capacity];
}
}
public int get(int key) {
if (capacity <= 0 || key < 0 || size == 0) {
return -1;
}
// 目标位置
int arrayIndex = key % capacity;
Node target = table[arrayIndex];
// 存在冲突不在目标位置
if (target == null || target.key != key) {
int nextLocation = (arrayIndex + 1) % capacity;
while (nextLocation != arrayIndex) {
Node node = table[nextLocation];
if (node != null && node.key == key) {
// 找到
target = node;
break;
} else {
nextLocation = (nextLocation + 1) % capacity;
}
}
}
if (target != null && target.key == key) {
// 维护双向链表,放到链表尾部
adjustNodeAccessLocation(target);
return target.value;
} else {
// 未找到
return -1;
}
}
public void put(int key, int value) {
if (capacity <= 0 || key < 0) {
return;
}
Node target = null;
// 查找当前节点是否存在,存在则更新,否则插入
for (int i = 0; i < table.length; i++) {
Node node = table[i];
if (node != null && node.key == key) {
target = node;
break;
}
}
if (target != null) {
// 更新
target.value = value;
adjustNodeAccessLocation(target);
} else {
// 新增
// 容量满了,删除最近最少访问的元素,即head节点
if (size == capacity) {
Node toDeletedNode = head;
head = head.next;
if (head == null) {
tail = null;
} else {
head.pre = null;
}
table[toDeletedNode.arrayIndex] = null;
toDeletedNode = null;
size--;
}
// 取模获取数组下标,如果已经存在元素,则往下查找直到查找到一个空闲位置,
// 注意是环形数组,直到当前数组下标的前一个位置
int arrayIndex = key % capacity;
Node node = table[arrayIndex];
Node newNode = new Node(key, value);
if (node == null) {
newNode.arrayIndex = arrayIndex;
table[arrayIndex] = newNode;
} else {
// 存在冲突,遍历直到找到一个空位置,环形数组故与capacity取模,避免越界问题
int nextLocation = (arrayIndex + 1) % capacity;
// 查找直到回到原位置
while (nextLocation != arrayIndex) {
if (table[nextLocation] == null) {
// 找到
newNode.arrayIndex = nextLocation;
table[nextLocation] = newNode;
break;
} else {
// 往下查找
nextLocation = (nextLocation + 1) % capacity;
}
}
}
size++;
target = newNode;
// 新节点追加到双向链表尾部
addNewNodeToTail(target);
}
}
private void adjustNodeAccessLocation(Node target) {
// 存在两个以上节点且不是访问尾结点
if (head != tail && target != tail) {
// 访问头结点
if (head == target) {
head = head.next;
head.pre = null;
tail.next = target;
target.pre = tail;
tail = target;
} else {
// 访问中间节点
// 调整当前节点的双向链表的前后节点
target.pre.next = target.next;
target.next.pre = target.pre;
// 当前节点放到链表尾部
tail.next = target;
target.pre = tail;
tail = target;
}
}
}
private void addNewNodeToTail(Node target) {
if (head == null && tail == null) {
head = tail = target;
} else {
tail.next = target;
target.pre = tail;
tail = target;
}
}
private class Node {
public int key;
public int value;
// 优化性能:当前在哈希表数组的下标,O(1)时间复杂度删除
public int arrayIndex;
public Node pre;
public Node next;
public Node() {}
public Node(int key, int value) {
this.key = key;
this.value = value;
this.pre = null;
this.next = null;
}
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/```
作者:服务端开发
来源:CSDN
赞
收藏
回复
相关推荐