LRU 最近最少使用算法

leetcode 同时被 3 个专栏收录
14 篇文章 0 订阅
8 篇文章 0 订阅
2 篇文章 0 订阅

百度百科:

​ LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

LeetCode:

146. LRU 缓存机制

大二学操作系统的时候学了LRU页面置换算法, 那时候只会用纸和笔手动模拟, 今天在看设计模式的策略模式的例子时看到了LRU evictionAlgo, 索性去leetcode找到此题, 起初真的是没思路, 看了题解后, 发现太妙了, 用一个map 和 双向链表, map的使用可以使取值时间复杂度降低到O(1) , 双向链表则可以模拟使用先后顺序

设计LRU Cash 数据结构

image-20211127121402178

设计方法

put方法:

  • 若存在key, 更新node的value值, 并将该node 移到链表头

  • 若存在key, 先检查是否达到容量.

    如果达到容量则删除链表尾元素.

    最后构造节点, 将节点添加到链表头, 并加入cashMap

get方法:

  • 不存在的key, 直接返回-1
  • 存在则将该node 移到链表头

代码实现

type LRUCache struct {
	capacity   int
	head, tail *DNode
	cashMap    map[int]*DNode
}

type DNode struct {
	prev       *DNode
	next       *DNode
	key, value int
}

func Constructor(capacity int) LRUCache {
	l := LRUCache{
		capacity: capacity,
		head:     initNode(0, 0),
		tail:     initNode(0, 0),
		cashMap:  map[int]*DNode{},
	}
	l.head.next = l.tail
	l.tail.prev = l.head
	return l
}

func (l *LRUCache) Get(key int) int {
	node, ok := l.cashMap[key]
	if !ok {
		return -1
	}
	l.removeToHead(node)
	return node.value
}

func (l *LRUCache) Put(key int, value int) {
	//存在
	if node, ok := l.cashMap[key]; ok {
		node.value = value
		l.removeToHead(node)
		return
	}
	//不存在
	if len(l.cashMap) >= l.capacity {
		removed := l.removeTail()
		delete(l.cashMap, removed.key)
	}
	node := initNode(key, value)
	l.cashMap[key] = node
	l.addNodeToHead(node)
}

func initNode(key, value int) *DNode {
	return &DNode{
		key:   key,
		value: value,
	}
}

func (l LRUCache) addNodeToHead(node *DNode) {
	node.prev = l.head
	node.next = l.head.next
	l.head.next.prev = node
	l.head.next = node
}

func (l LRUCache) removeTail() *DNode {
	node := l.tail.prev
	l.removeNode(node)
	return node
}

func (l LRUCache) removeToHead(node *DNode)  {
	l.removeNode(node)
	l.addNodeToHead(node)
}

func (l LRUCache) removeNode(node *DNode) {
	node.prev.next = node.next
	node.next.prev = node.prev
}

🎈 一开始自己实现双向链表, 后来想到GO语言中有container/list, 以前也没用过, 这次写一下试试如何

import "container/list"

type LRUCache struct {
	capacity    int
	dLinkedList *list.List
	cash        map[int]*list.Element
}

type DLinkNode struct {
	key, value int
}

func (d DLinkNode) Value() interface{} {
	return d.value
}

func Constructor(capacity int) LRUCache {
	return LRUCache{
		capacity:    capacity,
		dLinkedList: list.New(),
		cash:        map[int]*list.Element{},
	}
}

func (l *LRUCache) Get(key int) int {
	element, ok := l.cash[key]
	if !ok {
		return -1
	}
	l.dLinkedList.MoveToFront(element)
	return element.Value.(*DLinkNode).value
}

func (l *LRUCache) Put(key int, value int) {
	element, ok := l.cash[key]
	//如果key已存在
	if ok {
		node := element.Value.(*DLinkNode)
		node.value = value
		l.dLinkedList.MoveToFront(element)
	}
	//如果key不存在
	if len(l.cash) >= l.capacity {
		back := l.dLinkedList.Back()
		l.dLinkedList.Remove(back)
		delete(l.cash, back.Value.(*DLinkNode).key)
	}
	node := &DLinkNode{
		key:   key,
		value: value,
	}
	front := l.dLinkedList.PushFront(node)
	l.cash[key] = front
}

总结

  • 操作双向链表的时候要注意指向先后问题,比如: 写addNodeToHead的时候, l.head.next.prev = nodel.head.next = node的顺序一开始就搞错了, 找bug找了很长时间
  • Go语言内置的数据结构,不够熟悉, 第二段代码实现中map的值类型是*list.Element元素句柄, 这样便于调用list内置方法
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页

打赏作者

来深圳

你的鼓励是我创造的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值