算法就像搭乐高:带你手撸 LRU 算法 :: labuladong的算法小抄 #1058
Replies: 31 comments 6 replies
-
非常好~ |
Beta Was this translation helpful? Give feedback.
-
东哥真是666,想不出别的语言了 |
Beta Was this translation helpful? Give feedback.
-
private void removeLeastRecently() { cache.removeFirst() 有可能返回空值, 删除之前需要判空吗? |
Beta Was this translation helpful? Give feedback.
-
由於只有一種方法去插入而且每次都會檢查是否需要刪除, |
Beta Was this translation helpful? Give feedback.
-
@ngokchaoho 这是一种编程习惯罢了,可以改成 |
Beta Was this translation helpful? Give feedback.
-
JS代码供参考 class Node{
constructor(key,value){
this.key = key
this.value = value
}
}
class DoubleList{
constructor(){
// 头、尾的虚拟节点
this.head = new Node(0,0)
this.tail = new Node(0,0)
this.head.next = this.tail
this.tail.prev = this.head
this.size = 0
}
addLast(node){
node.prev = this.tail.prev
node.next = this.tail
this.size++
this.tail.prev.next = node
this.tail.prev = node
}
removeNode(node){
node.prev.next = node.next
node.next.prev = node.prev
this.size--
return node.key
}
removeFirst(){
if(this.head.next == this.tail)
return null
return this.removeNode(this.head.next)
}
}
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
this.hash = new Map()
this.cache = new DoubleList()
this.capacity = capacity
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
// console.log('get',key)
if(this.hash.has(key)){
// get要调整顺序
let moveNode = this.hash.get(key)
this.cache.removeNode(moveNode)
this.cache.addLast(moveNode)
return this.hash.get(key).value
}
else
return -1
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {
// console.log('push',key,value)
// hash表中已经有了
if(this.hash.has(key)){
let moveNode = this.hash.get(key)
this.cache.removeNode(moveNode)
let newNode = new Node(key,value)
this.cache.addLast(newNode)
this.hash.set(key,newNode)
}
// hash表中没有
else {
// 超出容量,删除头节点,然后追加在尾部
if(this.cache.size == this.capacity){
let deleted = this.cache.removeFirst()
this.hash.delete(deleted)
let newNode = new Node(key,value)
this.cache.addLast(newNode)
this.hash.set(key,newNode)
}
// 未超出容量,直接追加
else {
let newNode = new Node(key,value)
this.cache.addLast(newNode)
this.hash.set(key,newNode)
}
}
}; |
Beta Was this translation helpful? Give feedback.
-
上面代码没有把 makeRecently、addRecently 这些方法提出来,直接在LRU的get、put方法里操作 hashMap和双向链表了 不过应该还算能看懂(js真的..优先级队列、哈希链表都必须纯手撸 还是java刷题比较友好 |
Beta Was this translation helpful? Give feedback.
-
put()里面的makeRecently是不是可以不用啊 |
Beta Was this translation helpful? Give feedback.
-
要的,put 里的那个 makeRecently 是判断了 key 已经存在于目前数据结构中,第一次put只是更新值,应该不会改变位置,所以要再调 makeRecently 来把更新后的值重新 put 到链表尾部。 |
Beta Was this translation helpful? Give feedback.
-
太厉害了 |
Beta Was this translation helpful? Give feedback.
-
js...手撸 |
Beta Was this translation helpful? Give feedback.
-
TypeScript 实现 LRUclass DNode {
// 结点存储的数据为 key - value
public key: number;
public value: number;
// 指向前驱和后继
public prev: DNode | null;
public next: DNode | null;
constructor(key: number, value: number) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
}
/** 双向链表 */
class DoubleLinkedList {
// 虚拟头尾结点
private head: DNode;
private tail: DNode;
public size: number = 0;
constructor() {
// 初始化虚拟头尾结点
this.head = new DNode(-1, -1);
this.tail = new DNode(-1, -1);
this.head.next = this.tail;
this.tail.prev = this.head;
}
/**
* @description 在双向链表末尾插入结点
* @param node 待添加的结点
*/
public addLast(node: DNode): void {
// 将 node 插入到最后
node.prev = this.tail.prev;
node.next = this.tail;
// 将 tail 的指针修正
this.tail.prev!.next = node;
this.tail.prev = node;
this.size++;
}
/**
* @description 将结点从双向链表中移除
* @param node 待移除的结点
*/
public remove(node: DNode): void {
node.prev!.next = node.next;
node.next!.prev = node.prev;
this.size--;
}
/**
* @description 移除双向链表的第一个结点
*/
public removeFirst(): DNode | null {
if (this.head.next === this.tail) {
// 双向链表为空则没法移除
return null;
}
const first = this.head.next;
this.remove(first!);
return first;
}
}
class LRUCache {
private capacity: number;
private map: Map<number, DNode>;
private cache: DoubleLinkedList;
constructor(capacity: number) {
this.capacity = capacity;
this.map = new Map();
this.cache = new DoubleLinkedList();
}
private makeRecently(key: number): void {
// 从哈希表中获取到双向链表结点
const node = this.map.get(key);
if (node) {
// 将其从链表中删除
this.cache.remove(node);
// 再将其插入到尾部 表示最近使用过
this.cache.addLast(node);
}
}
/**
* @description 删除 key 的时候统一从哈希表和双向链表中删除
* @param key 待删除的 key
*/
private deleteKey(key: number): void {
const node = this.map.get(key);
if (node) {
this.map.delete(key);
this.cache.remove(node);
}
}
/**
* @description 添加元素并让其成为最近使用过的
*/
private addRecently(key: number, value: number): void {
const node = new DNode(key, value);
this.cache.addLast(node);
this.map.set(key, node);
}
/**
* @description 移除最近最久未使用的结点 要在 map 和链表中都移除
*/
private removeLeastRecently(): void {
const node = this.cache.removeFirst();
node && this.map.delete(node.key);
}
get(key: number): number {
const node = this.map.get(key);
if (node) {
this.makeRecently(key);
return node.value;
} else {
return -1;
}
}
put(key: number, value: number): void {
const node = this.map.get(key);
if (node) {
// 删除旧数据
this.deleteKey(key);
// 新插入的数据作为最近使用过的
this.addRecently(key, value);
} else {
// 新增的元素 要注意是否超出容量
if (this.cache.size >= this.capacity) {
this.removeLeastRecently();
}
// 把元素添加 作为最近使用过的键值对
this.addRecently(key, value);
}
}
} |
Beta Was this translation helpful? Give feedback.
-
js 的 map 天然具有 hash + 顺序的特性,可以用来实现 LRU,但是本文的思路值得学习,还是以本文答案为准吧 /*
* @lc app=leetcode.cn id=146 lang=typescript
*
* [146] LRU 缓存
* 利用 Map 键具有顺序的特性
*/
// @lc code=start
class LRUCache {
cache: Map<number, number> = new Map();
capacity: number = 0;
constructor(capacity: number) {
this.capacity = capacity;
}
get(key: number): number {
if (this.cache.has(key)) {
const v = this.cache.get(key);
// 先删除,再插入,放入最后的位置
this.cache.delete(key);
this.cache.set(key, v);
return v;
}
return -1;
}
put(key: number, value: number): void {
if (this.cache.has(key)) {
this.cache.delete(key);
} else {
if (this.cache.size >= this.capacity) {
// 最久的 key
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
}
this.cache.set(key, value);
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/
// @lc code=end |
Beta Was this translation helpful? Give feedback.
-
看了下 LinkedHashMap 实现,发现还可以这样写,挺有意思的: class LRUCache {
int cap;
LRULinkedHashMap<Integer, Integer> cache;
public LRUCache(int capacity) {
this.cap = capacity;
// 最后一个参数 true 表示按访问排序
this.cache = new LRULinkedHashMap<>(capacity, 0.75F, true);
}
public int get(int key) {
if (cache.get(key) == null) {
return -1;
}
return cache.get(key);
}
public void put(int key, int value) {
cache.put(key, value);
}
private class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
public LRULinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor, accessOrder);
}
@Override
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return size() > cap;
}
}
} |
Beta Was this translation helpful? Give feedback.
-
python实现 class LRUCache:
def __init__(self, capacity: int):
self.map = {}
self.cache = DoubleList()
self.cap = capacity
def get(self, key: int) -> int:
if key not in self.map:
return -1
self.makeRecently(key)
return self.map[key].val
def put(self, key: int, value: int) -> None:
if key in self.map:
self.deleteKey(key)
self.addRecently(key, value)
return
if self.cap == self.cache.size:
self.removeLeastRecently()
self.addRecently(key,value)
return
def makeRecently(self, key):
temp = self.map[key]
self.cache.remove(temp)
self.cache.addLast(temp)
def addRecently(self, key, val):
temp = Node(key, val)
self.cache.addLast(temp)
self.map[key] = temp
def deleteKey(self, key):
temp = self.map[key]
self.cache.remove(temp)
del self.map[key]
def removeLeastRecently(self):
temp = self.cache.removeFirst()
temp_key = temp.key
del self.map[temp_key]
class Node:
def __init__(self, key, val):
self.key = key
self.val = val
self.next = None
self.prev = None
class DoubleList:
def __init__(self):
self.head = Node(0,0)
self.tail = Node(0,0)
self.head.next = self.tail
self.tail.prev = self.head
self.size = 0
def addLast(self, new_node: Node):
new_node.prev = self.tail.prev
new_node.next = self.tail
self.tail.prev.next = new_node
self.tail.prev = new_node
self.size += 1
def remove(self, node: Node):
node.prev.next = node.next
node.next.prev = node.prev
node.prev = None
node.next = None
self.size -= 1
def removeFirst(self):
if self.head.next == self.tail:
return None
first = self.head.next
self.remove(first)
return first |
Beta Was this translation helpful? Give feedback.
-
东哥,你的makeRecently没有更新hash table里面的node, 你删了和更新了链表的node却没有更新table下面key对应的node |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
打卡! |
Beta Was this translation helpful? Give feedback.
-
打卡打卡!看完了,打算自己手写一遍! |
Beta Was this translation helpful? Give feedback.
-
我想问一下在get方法中为什么直接用LinkedHashMap中的get方法没有进行位置的变换?: “LinkedHashMap中重写了HashMap的get方法,不止会取出所索要的节点的值,而且会调整LinkedHashMap中内置的链表中该键所对应的节点的位置,将该节点置为链表的尾部。” 谢谢! |
Beta Was this translation helpful? Give feedback.
-
用c++比較傳統的方法實現 class LRUCache {
public:
LRUCache(int capacity):cache(), capacity(capacity){}
int get(int key) {
if(hmap.find(key) == hmap.end()) {
return -1;
}
cache.removeNode(hmap[key]);
cache.addNewNode(hmap[key]);
return hmap[key]->val;
}
void put(int key, int value) {
// 沒找到
if(hmap.find(key) == hmap.end()) {
if(cache.size() == capacity) {
node *oldnode = cache.removeFirstNode();
hmap.erase(oldnode->key);
delete(oldnode);
}
node *nnode = new node(key, value);
cache.addNewNode(nnode);
hmap.insert({key, nnode});
}
// 找到
else {
node *n = hmap[key];
n->val = value;
cache.removeNode(n);
cache.addNewNode(n);
}
}
private:
class node {
public:
node(int key, int val):key(key),val(val),prev(nullptr), next(nullptr){}
int val;
int key;
node *prev;
node *next;
};
class doublylinkedlist{
public:
doublylinkedlist():sz(0) {
head = new node(-1,-1);
tail = new node(-1,-1);
head->next = tail;
tail->prev = head;
}
void removeNode(node *rnode) {
rnode->next->prev = rnode->prev;
rnode->prev->next = rnode->next;
--sz;
}
void addNewNode(node *nnode) {
nnode->prev = tail->prev;
nnode->next = tail;
tail->prev->next = nnode;
tail->prev = nnode;
++sz;
}
node* removeFirstNode() {
if(head->next == tail) return nullptr;
node *rnode = head->next;
removeNode(rnode);
return rnode;
}
int size() {return sz;}
private:
node *head;
node *tail;
int sz;
};
unordered_map<int, node*> hmap;
doublylinkedlist cache;
int capacity;
}; |
Beta Was this translation helpful? Give feedback.
-
面试用LinkedlListHashMap不行吧? |
Beta Was this translation helpful? Give feedback.
-
// C++手动双向链表(没有内存泄漏)
|
Beta Was this translation helpful? Give feedback.
-
酷!是一个不算复杂但是实用的算法 |
Beta Was this translation helpful? Give feedback.
-
使用LinkedHashMap,不用自己写makeRecently,它有个属性accessOrder,初始设置为true,然后get访问时,内部就会把访问的值,提升到最近访问的。 |
Beta Was this translation helpful? Give feedback.
-
为什么前面说的需要用双链表实现,后面又突然切换回单链表,文中却只字未提转换的原因,这种没有语言过渡的感觉给人一种复制粘贴、毫无思考的廉价感。我发现后面的文章没有前面的文章打磨的细。当然,我只是提个建议,毕竟这个系列很棒! |
Beta Was this translation helpful? Give feedback.
-
这是来自QQ邮箱的自动回复邮件。你好,你的邮件我主人的QQ邮箱已收到,但是主人无法马上回复你的邮件。我会提醒主人,尽快给你回复。
|
Beta Was this translation helpful? Give feedback.
-
这是来自QQ邮箱的自动回复邮件。你好,你的邮件我主人的QQ邮箱已收到,但是主人无法马上回复你的邮件。我会提醒主人,尽快给你回复。
|
Beta Was this translation helpful? Give feedback.
-
完整代码: leetcode 146 通过// 节点设计
var node = function(k, v) {
this.key = k;
this.val = v;
this.prev = null;
this.next = null;
}
// 双链表设计
var DoubleList = function() {
// 头尾虚拟节点
this.head = new node(0,0);
this.tail = new node(0,0);
this.size = 0;
// 初始化双链表数据
this.head.next = this.tail;
this.tail.prev = this.head;
}
DoubleList.prototype.addLast = function(x) {
// 注意链接顺序
this.tail.prev.next = x;
x.next = this.tail;
x.prev = this.tail.prev;
this.tail.prev = x;
this.size++;
}
DoubleList.prototype.remove = function(x) {
x.prev.next = x.next;
x.next.prev = x.prev;
this.size--;
}
DoubleList.prototype.removeFirst = function() {
if(this.head.next == this.tail) {
return null;
}
var first = this.head.next;
this.remove(first);
return first;
}
DoubleList.prototype.size = function() {
return this.size;
}
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
this.map = new Map();
this.cache = new DoubleList();
this.cap = capacity;
this.makeRecently = function(key) {
var x = this.map.get(key);
// 先从链表中删除这个节点
this.cache.remove(x);
// 重新插到队尾
this.cache.addLast(x);
}
/* 添加最近使用的元素 */
this.addRecently = function(key, val) {
var x = new node(key, val);
// 链表尾部就是最近使用的元素
this.cache.addLast(x);
// 别忘了在 map 中添加 key 的映射
this.map.set(key, x);
}
/* 删除某一个 key */
this.deleteKey = function(key) {
var x = this.map.get(key);
// 从链表中删除
this.cache.remove(x);
// 从 map 中删除
this.map.delete(key);
}
/* 删除最久未使用的元素 */
this.removeLeastRecently = function() {
// 链表头部的第一个元素就是最久未使用的
var deletedNode = this.cache.removeFirst();
// 同时别忘了从 map 中删除它的 key
if(deletedNode != null) {
var deletedKey = deletedNode.key;
this.map.delete(deletedKey);
}
}
};
/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
if(!this.map.has(key)) {
return -1;
}
this.makeRecently(key);
return this.map.get(key).val;
};
/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, val) {
if (this.map.has(key)) {
// 删除旧的数据
this.deleteKey(key);
// 新插入的数据为最近使用的数据
this.addRecently(key, val);
return;
}
if (this.cap === this.cache.size) {
// 删除最久未使用的元素
this.removeLeastRecently();
}
// 添加为最近使用的元素
this.addRecently(key, val);
};
/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/ |
Beta Was this translation helpful? Give feedback.
-
这是来自QQ邮箱的自动回复邮件。你好,你的邮件我主人的QQ邮箱已收到,但是主人无法马上回复你的邮件。我会提醒主人,尽快给你回复。
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
文章链接点这里:算法就像搭乐高:带你手撸 LRU 算法
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions