我是靠谱客的博主 冷傲云朵,最近开发中收集的这篇文章主要介绍LRU 最近最少使用算法,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

????LRU 最近最少使用算法

    • 设计LRU Cash 数据结构
    • 设计方法
    • 代码实现
    • 总结

百度百科:

​ 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内置方法

最后

以上就是冷傲云朵为你收集整理的LRU 最近最少使用算法的全部内容,希望文章能够帮你解决LRU 最近最少使用算法所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(38)

评论列表共有 0 条评论

立即
投稿
返回
顶部