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

相关推荐
复杂网络1 小时前
多个 Claude Code 与多个 Codex 协同工作:设计与实现方案
算法
HjhIron17 小时前
面试常客:字符串算法从入门到进阶
算法·面试
吴佳浩18 小时前
DeepSeek DSpark:Confidence-Scheduled Speculative Decoding 技术解析
人工智能·算法·deepseek
触底反弹20 小时前
🧠 搞懂 Token,才算真正入门大模型——从分词原理到 Embedding 语义实战
javascript·人工智能·算法
vivo互联网技术1 天前
ICLR 2026 | 基于后验采样的图像恢复方法LearnIR:人脸去阴影、去雾
人工智能·算法·aigc
浮生望1 天前
JS字符串与回文算法:从包装类到双指针的面试进阶之路
javascript·算法
黄敬峰1 天前
面试必刷:从JS底层包装类到双指针,彻底搞懂字符串与回文算法
算法
地平线开发者1 天前
J6B vio scenario sample
算法
BothSavage2 天前
Trae远程开发中DeepSeek自定义模型4054错误的排查与修复
算法