关于 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

相关推荐
Web极客码13 分钟前
如何用三段式确定性剪枝,为 LLM Agent 砍掉 35% 的 Token 成本?
服务器·人工智能·算法·机器学习
程序猿乐锅17 分钟前
【数据结构与算法 | 第六篇】力扣1109,1094差分数组
java·算法·leetcode
wabs6661 小时前
关于图论【广度优先搜索的理论基础】
算法·图论·宽度优先
孤魂2331 小时前
逻辑回归算法
算法·机器学习·逻辑回归
weixin_377634841 小时前
【多模型预测】 如何合理融合多个预测分值
算法·机器学习·概率论·预测·agent预测
无风听海1 小时前
Claude Agent Skills 的四种设计模式;从渐进式披露到最小权限
java·算法·设计模式
37.2℃9951 小时前
专业的Claude Design解决方案
前端·算法
め.1 小时前
定点数运算库
算法·unity
dyxal2 小时前
最长公共子序列(LCS)
python·算法
稚南城才子,乌衣巷风流4 小时前
ST 表(Sparse Table)算法详解:原理、实现与应用
算法