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

相关推荐
Tairitsu_H1 分钟前
[LC优选算法#4] 滑动窗口 | 串联所有单词的⼦串 | 最⼩覆盖⼦串
c++·算法·滑动窗口
devilnumber3 分钟前
Java 二分查找(二分算法)详解 + 实战运用 + 核心坑点
java·开发语言·算法
洛水水5 分钟前
【力扣100题】84.字符串解码
算法·leetcode·职场和发展
MicroTech202532 分钟前
量子隐形传态路线的瓶颈与突破,微算法科技(MLGO)以技术创新助力量子通信长距离组网
科技·算法·量子计算
洛水水32 分钟前
【力扣100题】89.下一个排列
数据结构·算法·leetcode
洛水水33 分钟前
【力扣100题】90.寻找重复数
算法·leetcode·职场和发展
鱼子星_38 分钟前
【数据结构】排序的拓展——快速排序的生态多样性与归并排序沾染文件操作
c语言·数据结构·算法
alphaTao38 分钟前
LeetCode 每日一题 2026/6/8-2026/6/14
算法·leetcode
KaMeidebaby43 分钟前
卡梅德生物技术快报|噬菌体展示文库构建全流程解析 | 大豆球蛋白纳米抗体筛选实践
人工智能·python·tcp/ip·算法·机器学习
CC数学建模1 小时前
2026年第十六届APMCM 亚太地区大学生数学建模竞赛(中文赛项)赛题B题:高性能芯片热管理系统的优化问题完整思路、代码、模型、文章,全网首发高质量分享!
python·算法·数学建模