LRU算法-golang

1.思路

核心:实现一中最少访问淘汰策略的机制。

  a.以内要快速访问,查找,删除,查询操作要快,首先想到的就kv存储,即map数据结构。同时要删除和插入的时间复杂度o(1),链表具有该种特性。但是缓存中需要有顺序之分,而链表也是有序的,并且为了区分最近使用跟许久未使用的情况。使用头部作为最近使用的节点,而尾部都是许久未使用的,并且设置淘汰容量,当加入新元素,发现容量满了,就将许久未使用的淘汰掉,腾出空间,再存储新的即可。

  b.使用双向链表的原因是,移动节点到头部or淘汰元素时,需要前驱节点的支撑,所有在链表中使用双向链表。

  c.map控制缓存节点的快速访问,查找与删除,双向链表设置头尾指针控制访问时的节点移动到头部分,以及淘汰时的尾部节点的的淘汰处理。

  d.新加入节点,直接头插法插入链表中。访问某个节点,并将该节点设置为最新访问的头节点。更新节点,同样需要将该节点移动到头部,设置成最新访问节点。

  e.存储的结构图形:

2.代码:

package main

import (
	"fmt"
)

type Node struct {
	Key, Val   interface{}
	Prev, Next *Node
}

type LRUCache struct {
	Size       int // 节点的数量
	Cap        int // 容量
	Cache      map[interface{}]*Node
	Head, Tail *Node // 头尾节点

}

func CreateCache(cap int) *LRUCache {
	return &LRUCache{
		Size:  0,
		Cap:   cap,
		Cache: map[interface{}]*Node{},
		Head:  nil,
		Tail:  nil,
	}
}

// 设置当前节点为新的头节点
func (l *LRUCache) SetHead(node *Node) {
	node.Next = l.Head
	node.Prev = nil
	if l.Head == nil {
		l.Head = node
	} else {
		l.Head.Prev = node
		l.Head = node
	}
	if l.Tail == nil {
		l.Tail = node
	}
}

// 移动当前节点到头节点
func (l *LRUCache) MoveNodeToHead(node *Node) {
	// 更新节点,头节点不为空,并且移动的节点非头节点
	// 如果是本身就是头节点,或者此时头尾相同,不作处理,如果是尾节点,需要移动
	if l.Head != node && l.Tail == node {
		// Tail的前缀节点跟Tail断链
		prefix := l.Tail.Prev
		l.Tail.Prev = nil
		prefix.Next = nil
		// 移动至头
		l.Tail.Next = l.Head
		l.Head.Prev = l.Tail
		l.Tail = prefix // 尾部节点上移动
	}
	// 处于中间节点
	if l.Head != node && l.Tail != node {
		// 拆出来node
		prefix := node.Prev
		prefix.Next = node.Next
		node.Next.Prev = prefix

		// 移动到头部
		node.Next = l.Head
		node.Prev = nil
		l.Head.Prev = node
		l.Head = node
	}
}

// Add Node
func (l *LRUCache) AddNode(key, val interface{}) {
	if _, ok := l.Cache[key]; ok {
		// 如果存在,直接更新
		l.Cache[key].Val = val
		// 移动该节点至头部
		l.MoveNodeToHead(l.Cache[key])
	} else {
		node := &Node{
			Key: key,
			Val: val,
		}
		// 检查容量是否超标
		if l.Size >= l.Cap {
			delNode := l.RemoveLastNode() // 链表断链
			delete(l.Cache, delNode.Key)  // Cache清理
			l.Size--                      // 节点数量递减
		}
		// map中存储节点
		l.Cache[key] = node
		// 将节点移动到头部
		l.SetHead(node)
		// 节点数量
		l.Size++
	}
}

// 移除很久没访问的节点,腾出空间
func (l *LRUCache) RemoveLastNode() *Node {
	delNode := l.Tail
	if l.Tail != nil {
		l.Tail = l.Tail.Prev   // tail指向最近没访问的倒数第二节点
		l.Tail.Next.Prev = nil // 当前节点的prev断链
		l.Tail.Next = nil
	}
	return delNode
}

// 访问某个节点,访问之后,同样将访问的节点移动到头部
func (l *LRUCache) GetNode(key interface{}) *Node {
	var retNode *Node
	if _, ok := l.Cache[key]; ok {
		retNode = l.Cache[key]
		// 移动当前节点至头部
		l.MoveNodeToHead(l.Cache[key])
	}
	return retNode
}

func (l *LRUCache) Show() {
	for k, _ := range l.Cache {
		fmt.Println(l.Cache[k].Key, "=", l.Cache[k].Val)
	}
}

func (l *LRUCache) ShowList() {
	for h := l.Head; h != nil; h = h.Next {
		fmt.Println(h.Key, "+", h.Val)
	}
}

func main() {

	// 创建一个哈希链表
	lru := CreateCache(2)

	// lru.AddNode(1, 1)
	// lru.AddNode(2, 2)
	// lru.AddNode(3, 3) // 调用淘汰
	// lru.AddNode(4, 4) // 调用淘汰

	for i := 0; i < 10; i++ {
		lru.AddNode(i, i)
	}
	// lru.Show()
	lru.ShowList()

}

运行结果:

9 + 9
8 + 8

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:护眼 设计师:闪电赇1 返回首页
评论

打赏作者

一颗简单的心

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值