关于 lru 和 lfu算法的简单实现

关于 lru 和 lfu的简单实现

前言

lru和lfu是两种经典的内存淘汰算法,前者是淘汰最近最久未使用的空间 ,后者是淘汰最不经常使用 。乍一听感觉差不多,其实还是有些区别的。lru 关注的是时间先后,优先淘汰时间范围内最不经常用的空间 。 lfu 关注的是已使用的次数,优先淘汰那些使用次数最少的空间。

在实际的应用中,lru 要使用的多些,因为对于大部分情况,最近用到的,未来用到的概率比较大。 lfu 主要是应对lru 无法应对 那些以前的热点数据,但是由于某些原因最近不常用,而被淘汰的场景。

说的好,不如做得好。 下面对照 leecode 的两个题实现了两个简易版本。


一、最近最久未使用(LRU)

LRU

代码如下:

go 复制代码
package main

import "container/list"

type LRUCacheNode struct {
	Key   int
	Value int
}
type LRUCache struct {
	LRUMap    map[int]*list.Element
	ValueList *list.List
	Capacity  int
}

func Constructor(capacity int) LRUCache {
	return LRUCache{
		LRUMap:    make(map[int]*list.Element),
		ValueList: list.New(),
		Capacity:  capacity,
	}
}

func (this *LRUCache) getNode(key int) *LRUCacheNode {
	elem, ok := this.LRUMap[key]
	if !ok {
		return nil
	}

	// visit key, move to front of list
	this.ValueList.MoveToFront(elem)
	elemNode := elem.Value.(*LRUCacheNode)
	return elemNode
}

func (this *LRUCache) Get(key int) int {
	node := this.getNode(key)
	if node == nil {
		return -1
	}
	return node.Value
}

func (this *LRUCache) Put(key int, value int) {
	node := this.getNode(key)
	if node != nil {
		node.Value = value
		return
	}

	// if full, delete the Least Recently Used node
	if len(this.LRUMap) >= this.Capacity {
		backElem := this.ValueList.Back()
		this.ValueList.Remove(backElem)
		backNode := backElem.Value.(*LRUCacheNode)
		delete(this.LRUMap, backNode.Key)
	}

	newNode := &LRUCacheNode{
		Key:   key,
		Value: value,
	}

	newElem := this.ValueList.PushFront(newNode)
	this.LRUMap[key] = newElem
	return
}

lru 关注的是时间维度,因此需要一个类似先进先出的list 来充当时间维度,把最近访问的放到前面,后面就是最久未使用的。

二、最不经常使用(LFU)

LFU

代码如下:

go 复制代码
package main

import (
	"container/list"
	"fmt"
	"sort"
	"strings"
)

type LFUCache struct {
	ValueMap map[int]*list.Element
	FreMap   map[int]*list.List

	Capacity int
	MinFre   int
}



type LFUListNode struct {
	Key    int
	Value  int
	CurFre int
}

func Constructor(capacity int) LFUCache {
	return LFUCache{
		ValueMap: make(map[int]*list.Element),
		FreMap:   make(map[int]*list.List),
		Capacity: capacity,
		MinFre:   0,
	}
}

// get node from lfu cache
func (this *LFUCache) getNode(key int) *LFUListNode {
	elem, ok := this.ValueMap[key]
	if !ok {
		return nil
	}

	lfuListNode, _ := elem.Value.(*LFUListNode)
	curFre := lfuListNode.CurFre
	curList, ok := this.FreMap[curFre]
	if !ok {
		panic("not found")
	}

	curList.Remove(elem)
	newElem := this.addNodeToNextFre(lfuListNode)
	// node: must set the newElem because elem is not equal to newElem
	this.ValueMap[key] = newElem

	// update min Fre curList is empty after move
	if curList.Len() == 0 && curFre == this.MinFre {
		this.updateMinFre()
	}
	return lfuListNode
}

func (this *LFUCache) updateMinFre() {
	this.MinFre++
}

func (this *LFUCache) addNodeToNextFre(lfuListNode *LFUListNode) *list.Element {
	lfuListNode.CurFre++
	nextFreList, ok := this.FreMap[lfuListNode.CurFre]
	if !ok {
		nextFreList = list.New()
		this.FreMap[lfuListNode.CurFre] = nextFreList
	}

	// add to the front of next minFreMap list
	nextFreList.PushFront(lfuListNode)
	return nextFreList.Front()

}

func (this *LFUCache) addNewElem(key int, value int) {
	newLFUNode := &LFUListNode{
		Key:   key,
		Value: value,
		// new elem Fre is 1
		CurFre: 1,
	}

	_, ok := this.FreMap[newLFUNode.CurFre]
	if !ok {
		this.FreMap[newLFUNode.CurFre] = list.New()
	}

	this.FreMap[newLFUNode.CurFre].PushFront(newLFUNode)
	newElem := this.FreMap[newLFUNode.CurFre].Front()
	this.ValueMap[key] = newElem
}

func (this *LFUCache) Get(key int) int {
	lfuListNode := this.getNode(key)
	if lfuListNode == nil {
		return -1
	}

	return lfuListNode.Value
}

func (this *LFUCache) Put(key int, value int) {
	lfuNode := this.getNode(key)
	if lfuNode != nil {
		lfuNode.Value = value
		return
	}

	// already full, delete one elem
	if len(this.ValueMap) >= this.Capacity {
		minFreList, minFreOk := this.FreMap[this.MinFre]
		if !minFreOk {
			panic("not found")
		}

		exitElem := minFreList.Back()
		lfuNode := exitElem.Value.(*LFUListNode)
		minFreList.Remove(exitElem)
		delete(this.ValueMap, lfuNode.Key)

	}

	this.addNewElem(key, value)
	//  min fre is 1, because we add a new elem
	this.MinFre = 1
	return
}

lfu 算法要稍微难点,因为要记住每个 key 对应的使用频率,这是一个 map ,frequency对应的list,因为同一个频率会有多个 key。 同时还要维护一个最小频率,因为在删除的时候要优先从最小频率对应的那个 list 中删除最后一个元素。


参考

【1】LRU

【2】LFU

相关推荐
Nil2081 天前
leetcode 198打家劫舍
算法·leetcode·职场和发展
Shaoshing1 天前
前缀和与差分
java·数据结构·算法·前缀和·差分
loulanyue_1 天前
构建房产认知决策智能:Harness底座与AI自进化的技术实践——读贝壳首席算法科学家尹俊希2026云栖专场演讲
人工智能·算法
碧海银沙音频科技研究院1 天前
基于炬芯ATS录音卡BLE控制协议
人工智能·嵌入式硬件·算法·语音识别
suhan1 天前
【数据结构】三、栈和队列:6.链队列、双端队列、队列的应用(树的层次遍历、广度优先BFS、先来先服务FCFS)
数据结构·后端·算法
姜鱼问生1 天前
Docker Volume 备份与恢复实战:不停机迁移数据
后端·算法·架构
suhan1 天前
【数据结构】三、栈和队列:5.顺序队列(循环队列)(初始化,判空判满,入队,出队,实例)
数据结构·后端·算法
suhan1 天前
【数据结构】三、栈和队列:3.链栈(链栈栈的初始化,判空,进栈,出栈,读取栈顶,链栈实例)
数据结构·后端·算法
大雄很用心1 天前
如何理解时间复杂度与空间复杂度?| 面试刷题笔记02
算法
姜鱼问生1 天前
服务器磁盘满了?从告警到清理的完整排查
后端·算法·架构