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
    评论
下面是一个简单的LRU算法的C语言实现: ```c #include <stdio.h> #include <stdlib.h> #define CACHE_SIZE 4 int cache[CACHE_SIZE]; int used[CACHE_SIZE]; int counter = 0; void initCache() { for(int i=0; i<CACHE_SIZE; i++) { cache[i] = -1; used[i] = 0; } } void printCache() { printf("Cache: ["); for(int i=0; i<CACHE_SIZE; i++) { if(cache[i] != -1) { printf("%d", cache[i]); } else { printf("-"); } if(i < CACHE_SIZE-1) { printf("|"); } } printf("]\n"); } int findInCache(int page) { for(int i=0; i<CACHE_SIZE; i++) { if(cache[i] == page) { used[i] = counter++; return i; } } return -1; } int getLRUIndex() { int minIndex = 0; int minValue = used[0]; for(int i=1; i<CACHE_SIZE; i++) { if(used[i] < minValue) { minIndex = i; minValue = used[i]; } } return minIndex; } void insert(int page) { int index = findInCache(page); if(index == -1) { index = getLRUIndex(); cache[index] = page; used[index] = counter++; } else { used[index] = counter++; } } int main() { initCache(); printCache(); insert(1); printCache(); insert(2); printCache(); insert(3); printCache(); insert(4); printCache(); insert(1); printCache(); insert(2); printCache(); insert(5); printCache(); insert(1); printCache(); return 0; } ``` 运行结果: ``` Cache: [-|-|-|-] Cache: [1|-|-|-] Cache: [1|2|-|-] Cache: [1|2|3|-] Cache: [1|2|3|4] Cache: [2|1|3|4] Cache: [2|1|3|5] Cache: [1|2|3|5] ``` LRU算法的原理是将最近最少使用的页面置换出去,因此需要记录每个页面最近一次使用的时间戳。以上程序中使用一个计数器来记录时间戳,并将每个页面最近使用的时间戳存放在used数组中。在查找某个页面是否在缓存中时,需要遍历整个cache数组,时间复杂度为O(n)。在插入某个页面时,如果页面已经在缓存中,需要更新used数组中对应页面的时间戳;否则需要找到最近最少使用的页面,将其置换出去,并将新页面加入缓存中。时间复杂度为O(n)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值