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

相关推荐
郝学胜-神的一滴5 小时前
干货版《算法导论》05:从集合接口到排序
开发语言·数据结构·c++·程序人生·算法·排序
菜菜的顾清寒5 小时前
力扣Hot100(23)反转链表
算法·leetcode·链表
Michelle80235 小时前
基于随机森林的乳腺癌肿瘤分类实验
算法·随机森林·分类
Yvonne爱编码5 小时前
机器学习---聚类四大算法完整实验教程(层次 / K-Means/GMM/ 谱聚类)
算法·机器学习·聚类
東隅已逝,桑榆非晚5 小时前
C语言内存函数
c语言·开发语言·笔记·算法
深蓝电商API5 小时前
爬虫代理IP智能调度:基于响应速度的实时评分算法
爬虫·算法
汉克老师5 小时前
GESP5级C++考试语法知识(十七、二分算法提高篇(一))
c++·算法·二分算法·gesp5级·gesp五级·二分算法易错点
灵智实验室5 小时前
PX4状态估计技术EKF2详解(五):EKF2 故障检测、重置与鲁棒性——从单实例到多实例仲裁
算法·无人机·px 4
programhelp_5 小时前
Roblox Coding OA 面经分享|题量不小,但整体更偏工程思维
人工智能·算法·面试