连续两天被问到lru算法
第一次被问到的时候又惊又喜 毕竟之前在LeetCode上刷到过
放链接:力扣算法题地址
但是第一次答没完美答出来
导致睡觉的时候lru算法像放电影似的在脑子里放映......😭
这个算法我所了解的使用场景有
- redis的缓存淘汰策略
- mysql的buffer pool页面置换算法
重要肯定是相当重要 毕竟在很多重要的组件中都有其身影
这边附上算法实现(golang)版本
package main
/*
* @lc app=leetcode.cn id=146 lang=golang
*
* [146] LRU 缓存机制
*/
// @lc code=start
type LRUCache struct {
size int
capacity int
cache map[int]*DLinkedNode
head, tail *DLinkedNode
}
type DLinkedNode struct {
key, value int
prev, next *DLinkedNode
}
func initDLinkedNode(key, value int) *DLinkedNode {
return &DLinkedNode{
key: key,
value: value,
}
}
func Constructor_(capacity int) LRUCache {
l := LRUCache{
cache: map[int]*DLinkedNode{},
head: initDLinkedNode(0, 0),
tail: initDLinkedNode(0, 0),
capacity: capacity,
}
l.head.next = l.tail
l.tail.prev = l.head
return l
}
func (this *LRUCache) Get(key int) int {
if _, ok := this.cache[key]; !ok {
return -1
}
node := this.cache[key]
this.moveToHead(node)
return node.value
}
func (this *LRUCache) Put(key int, value int) {
if _, ok := this.cache[key]; !ok {
node := initDLinkedNode(key, value)
this.cache[key] = node
this.addToHead(node)
this.size++
if this.size > this.capacity {
removed := this.removeTail()
delete(this.cache, removed.key)
this.size--
}
} else {
node := this.cache[key]
node.value = value
this.moveToHead(node)
}
}
func (this *LRUCache) addToHead(node *DLinkedNode) {
node.prev = this.head
node.next = this.head.next
this.head.next.prev = node
this.head.next = node
}
func (this *LRUCache) removeNode(node *DLinkedNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
func (this *LRUCache) moveToHead(node *DLinkedNode) {
this.removeNode(node)
this.addToHead(node)
}
func (this *LRUCache) removeTail() *DLinkedNode {
node := this.tail.prev
this.removeNode(node)
return node
}
/**
* Your LRUCache object will be instantiated and called as such:
* obj := Constructor(capacity);
* param_1 := obj.Get(key);
* obj.Put(key,value);
*/
// @lc code=end
需要解决两个矛盾:如何高效的读(get)与写(write)
高效意味着时间度复杂度为o(1)
需要使得数据结构当自身容量满了的情况下能把最近最不常使用的那个键给剔除掉
核心思想就是
- 每次读或者写一个键,就相应的把改键往前挪,当容量满且需要新添加元素的时候删除最后那个元素(表示最久不曾使用过),同时将新添加的元素添加至队头
思考:相当于一个数组或者链表,但是由于数组挪动元素不方便,需要搬运大量的元素。所以采用链 表的方式,最新操作的永远放在表头。但是链表有缺点:查找元素很慢。为了解决查找元素慢的痛 点,需要给结构体添加一个map结构,map进行键值定位时间复杂度为o(1)。这样就可以使链表能实 现o(1)时间复杂度的读跟写
关键点来了........
我突然想起了两年前面试中面试官问我: 如果让你去实现一个有序的golang map,你会怎么设计
其实代码可以完全复用,核心思想一模一样!只不过每次在map中insert一个元素,是往队尾插...
总结:map+双向链表
- 新加元素往队头插 可以实现lru
- 新加元素往队尾插 实现的就是一个有序的map
我愿称之为黄金组合!
特此发个帖子记录一下
希望能对一些朋友有所启发, 也不知道是不是因为我太菜了才会因为把两个东西联系到一起才这么激动- -