微派三面的时候,面试官问了LRU-K,当时没实现出来,受益良多,事后去魔改了下LeetCode146-LRU题目
请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。
函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。
比起这道题,我多设计了一个History队列~~
敲出来简单实现了下,以后有机会或者认识更加深入之后再重新总结写一篇文章,以下附一下我的思路:
其实history队列相当于LRU_Cache,只是需要维护一个K值。于是我多附加了一个map[int]int,用于记录对应Key被调用了多少次!当时面试官提供了结构体维护的思路,其实都可以,可能map会耗费多点性能?(存疑)
LRU其实就是LRU-1!!!
相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。详细实现如下
(1). 数据第一次被访问,加入到访问历史列表;
(2). 如果数据在访问历史列表里后没有达到K次访问,则按照一定规则(FIFO,LRU)淘汰;
(3). 当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列删除,将数据移到缓存队列中,并缓存此数据,缓存队列重新按照时间排序;
(4). 缓存数据队列中被再次访问后,重新排序;
(5). 需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即:淘汰“倒数第K次访问离现在最久”的数据。
LRU-K具有LRU的优点,同时能够避免LRU的缺点,实际应用中LRU-2是综合各种因素后最优的选择,LRU-3或者更大的K值命中率会高,但适应性差,需要大量的数据访问才能将历史访问记录清除掉。
package main
import "fmt"
type LRUCache struct {
size int
capacity int
cache map[int]*DLinkNode
Head, Tail *DLinkNode
his *History_cache
}
type History_cache struct {
k int
size int
capacity int
his_cache map[int]*DLinkNode
Head, Tail *DLinkNode
exist_nums map[int]int
}
type DLinkNode struct {
key,value int
Pre, Next *DLinkNode
}
func InitDlinkNode(key, value int) *DLinkNode {
return &DLinkNode{key,value,nil,nil}
}
func Constructor_LRU(capacity int, h *History_cache) LRUCache {
l := LRUCache{
0,
capacity,
map[int]*DLinkNode{},
InitDlinkNode(0, 0),
InitDlinkNode(0, 0),
h,
}
l.Head.Next = l.Tail
l.Tail.Pre = l.Head
return l
}
func Constructor_His(capacity, k int) History_cache {
h := History_cache{
k,
0,
capacity,
map[int]*DLinkNode{},
InitDlinkNode(0, 0),
InitDlinkNode(0, 0),
map[int]int{},
}
h.Head.Next = h.Tail
h.Tail.Pre = h.Head
return h
}
func (lru *LRUCache) Get(key int) int {
if _,ok := lru.cache[key];!ok {
return -1
}
node := lru.cache[key]
lru.UpdatetoHead(node, lru.Head)
return node.value
}
func (lru *LRUCache) Put(key int, value int) {
//判断lru缓存中有无key
if _,ok := lru.cache[key];!ok {
//如果无key,判断在历史队列中有无
if _, ok2 := lru.his.his_cache[key];!ok2{
//如果无,则新增
node := InitDlinkNode(key, value)
lru.his.exist_nums[key]++
if lru.his.size >= lru.his.capacity {
lru.DeleteLast(&lru.his.size, lru.his.his_cache, lru.his.Tail)
}
lru.his.his_cache[key] = node
lru.InsertNewHead(&lru.his.size, node, lru.his.Head)
}else{
//如果有,则分两种情况 1.<k 2.出历史队列,进入lru缓存
lru.his.exist_nums[key]++
if lru.his.exist_nums[key]>=lru.his.k{
node := lru.his.Head
for node.Next!=lru.his.his_cache[key]{
node = node.Next
}
node.Next = node.Next.Next
lru.his.size--
delete(lru.his.his_cache, key)
delete(lru.his.exist_nums, key)
if lru.size>=lru.capacity{
lru.DeleteLast(&lru.size, lru.cache, lru.Tail)
}
new_node := InitDlinkNode(key, value)
lru.cache[key] = new_node
lru.InsertNewHead(&lru.size, new_node, lru.Head)
}else{
node := lru.his.his_cache[key]
node.value = value
lru.UpdatetoHead(node, lru.his.Head)
}
}
}else {
node := lru.cache[key]
node.value = value
lru.UpdatetoHead(node, lru.Head)
}
}
func (lru *LRUCache) UpdatetoHead(node *DLinkNode, linkHead *DLinkNode) {
node.Pre.Next = node.Next
node.Next.Pre = node.Pre
temp := linkHead.Next
linkHead.Next = node
node.Pre = linkHead
node.Next = temp
temp.Pre = node
}
func (lru *LRUCache) DeleteLast(size *int, c map[int]*DLinkNode, linkTial *DLinkNode) {
node := linkTial.Pre
linkTial.Pre = node.Pre
node.Pre.Next = node.Next
node.Pre = nil
node.Next = nil
(*size)--
delete(c, node.key)
}
func (lru *LRUCache) InsertNewHead(size *int, node, linkHead *DLinkNode) {
temp := linkHead.Next
linkHead.Next = node
node.Pre = linkHead
temp.Pre = node
node.Next = temp
(*size)++
}
func main() {
his := Constructor_His(2, 2)
lru := Constructor_LRU(2, &his)
fmt.Println("----未输入数据前----")
fmt.Println("历史队列的长度:", lru.his.size)
fmt.Println("LRU缓存的长度:", lru.size)
fmt.Println()
fmt.Println("----第一次PUT(1, 2)----")
lru.Put(1, 2)
fmt.Println("历史队列的长度:", lru.his.size)
fmt.Println("LRU缓存的长度:", lru.size)
fmt.Println("第一次GET:", lru.Get(1))
fmt.Println("历史队列对应值出现次数:", lru.his.exist_nums[1])
fmt.Println()
fmt.Println("----第二次次PUT(1, 2)----")
lru.Put(1, 2)
fmt.Println("历史队列的长度:", lru.his.size)
fmt.Println("LRU缓存的长度:", lru.size)
fmt.Println("第二次GET:", lru.Get(1))
fmt.Println("历史队列对应值出现次数:", lru.his.exist_nums[1])
}
这台电脑windows下没装环境,所以用白板敲了之后,在菜鸟go在线工具下编译的~~~~