LRU 最近最少使用算法

LRU 最近最少使用算法,第1张

LRU 最近最少使用算法

LRU 最近最少使用算法
    • 设计LRUCash数据结构
    • 设计方法
    • 代码实现
    • 总结

百度百科:

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

LeetCode:

​ 146. LRU 缓存机制

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

设计LRUCash数据结构

设计方法

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 = node 和 l.head.next = node的顺序一开始就搞错了, 找bug找了很长时间
  • Go语言内置的数据结构,不够熟悉, 第二段代码实现中map的值类型是*list.Element元素句柄, 这样便于调用list内置方法

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/zaji/5611771.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-12-15
下一篇2022-12-15

发表评论

登录后才能评论

评论列表(0条)

    保存