介绍一下LRU算法,并尝试实现

一、LRU(Least Recently Used)简介

LRU 是一种缓存淘汰算法,其核心思想是当缓存容量达到上限时,移除那些最近最少使用的数据项。换句话说,LRU 算法认为最近没有被访问的数据在未来被访问的可能性也比较低,因此可以优先被淘汰。

二、实现一个 LRU 缓存

实现一个高效的 LRU 缓存通常需要考虑快速查找、插入和删除操作。常用的方法是结合哈希表(用于O(1)时间复杂度的查找)和双向链表(用于维护元素的使用顺序,并支持O(1)时间复杂度的插入与删除)。下面是一个简单的实现思路:

1. 数据结构设计
  • 使用双向链表来存储缓存中的数据项,以便我们可以快速地在任意位置插入和删除节点。
  • 使用哈希表将键映射到链表中的节点,从而支持 O(1) 的查找时间。
2. 主要操作
  • get(key): 如果键存在于缓存中,则返回对应的值,并将该节点移到链表头部(表示最新访问)。如果不存在,则返回 -1。
  • put(key, value): 如果键已经存在,则覆盖旧值,并将该节点移到链表头部。如果键不存在,则添加新节点到链表头部。如果添加后超过最大容量,则移除链表尾部的节点(最久未使用)。
3. 示例代码(Python)
class DLinkedNode:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None


class LRUCache:

    def __init__(self, capacity: int):
        self.cache = dict() # 哈希表,键 -> 节点引用
        # 使用伪头部和伪尾部节点    
        self.head = DLinkedNode()
        self.tail = DLinkedNode()
        self.head.next = self.tail
        self.tail.prev = self.head
        self.capacity = capacity
        self.size = 0

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        # 如果 key 存在,先通过哈希表定位,再移到头部
        node = self.cache[key]
        self.moveToHead(node)
        return node.value

    def put(self, key: int, value: int) -> None:
        if key not in self.cache:
            # 如果 key 不存在,创建一个新的节点
            node = DLinkedNode(key, value)
            # 添加进哈希表
            self.cache[key] = node
            # 添加至双向链表的头部
            self.addToHead(node)
            self.size += 1
            if self.size > self.capacity:
                # 如果超出容量,删除双向链表的尾部节点
                removed = self.removeTail()
                # 删除哈希表中对应的项
                self.cache.pop(removed.key)
                self.size -= 1
        else:
            # 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node = self.cache[key]
            node.value = value
            self.moveToHead(node)

    def addToHead(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def removeNode(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev

    def moveToHead(self, node):
        self.removeNode(node)
        self.addToHead(node)

    def removeTail(self):
        node = self.tail.prev
        self.removeNode(node)
        return node
4. 关键方法解释
  • addToHead(node):将指定节点加到双向链表的头部。
  • removeNode(node):从双向链表中删除指定节点。
  • moveToHead(node):先删除节点再将其添加到链表头部,表示最近访问。
  • removeTail():移除链表尾部节点,并返回该节点,用于淘汰策略。

这种实现方式保证了所有关键操作(如 getput)的时间复杂度均为 O(1),非常适合于实现高效且动态变化的 LRU 缓存系统。

下面我将分别用 PHPGo 实现一个 LRU缓存(Least Recently Used),基于你上面提到的使用 哈希表 + 双向链表 的方式。

5. 其他语言实现
✅ (一)PHP 实现 LRU 缓存
<?php

class DLinkedNode {
    public $key;
    public $value;
    public $prev;
    public $next;

    public function __construct($key = null, $value = null) {
        $this->key = $key;
        $this->value = $value;
        $this->prev = null;
        $this->next = null;
    }
}

class LRUCache {
    private $capacity;
    private $cache = [];
    private $head;
    private $tail;
    private $size = 0;

    public function __construct($capacity) {
        $this->capacity = $capacity;
        $this->head = new DLinkedNode();
        $this->tail = new DLinkedNode();
        $this->head->next = $this->tail;
        $this->tail->prev = $this->head;
    }

    public function get($key) {
        if (!isset($this->cache[$key])) {
            return -1;
        }
        $node = $this->cache[$key];
        $this->moveToHead($node);
        return $node->value;
    }

    public function put($key, $value) {
        if (!isset($this->cache[$key])) {
            $node = new DLinkedNode($key, $value);
            $this->cache[$key] = $node;
            $this->addToHead($node);
            $this->size++;
            if ($this->size > $this->capacity) {
                $removed = $this->removeTail();
                unset($this->cache[$removed->key]);
                $this->size--;
            }
        } else {
            $node = $this->cache[$key];
            $node->value = $value;
            $this->moveToHead($node);
        }
    }

    private function addToHead($node) {
        $node->prev = $this->head;
        $node->next = $this->head->next;
        $this->head->next->prev = $node;
        $this->head->next = $node;
    }

    private function removeNode($node) {
        $node->prev->next = $node->next;
        $node->next->prev = $node->prev;
    }

    private function moveToHead($node) {
        $this->removeNode($node);
        $this->addToHead($node);
    }

    private function removeTail() {
        $node = $this->tail->prev;
        $this->removeNode($node);
        return $node;
    }
}

// 示例
$cache = new LRUCache(2);

$cache->put(1, 1);
$cache->put(2, 2);
echo $cache->get(1) . "\n"; // 返回 1
$cache->put(3, 3);         // 淘汰 key=2
echo $cache->get(2) . "\n"; // 返回 -1
$cache->put(4, 4);         // 淘汰 key=1
echo $cache->get(1) . "\n"; // 返回 -1
echo $cache->get(3) . "\n"; // 返回 3
echo $cache->get(4) . "\n"; // 返回 4
✅ (二)Go 实现 LRU 缓存
package main

import "fmt"

type DLinkedNode struct {
	key   int
	value int
	prev  *DLinkedNode
	next  *DLinkedNode
}

type LRUCache struct {
	capacity int
	cache    map[int]*DLinkedNode
	head     *DLinkedNode
	tail     *DLinkedNode
	size     int
}

func Constructor(capacity int) LRUCache {
	lru := LRUCache{
		capacity: capacity,
		cache:    make(map[int]*DLinkedNode),
		head:     &DLinkedNode{},
		tail:     &DLinkedNode{},
		size:     0,
	}
	lru.head.next = lru.tail
	lru.tail.prev = lru.head
	return lru
}

func (c *LRUCache) Get(key int) int {
	if node, ok := c.cache[key]; !ok {
		return -1
	} else {
		c.moveToHead(node)
		return node.value
	}
}

func (c *LRUCache) Put(key, value int) {
	if node, ok := c.cache[key]; !ok {
		node = &DLinkedNode{key: key, value: value}
		c.cache[key] = node
		c.addToHead(node)
		c.size++
		if c.size > c.capacity {
			removed := c.removeTail()
			delete(c.cache, removed.key)
			c.size--
		}
	} else {
		node.value = value
		c.moveToHead(node)
	}
}

func (c *LRUCache) addToHead(node *DLinkedNode) {
	node.prev = c.head
	node.next = c.head.next
	c.head.next.prev = node
	c.head.next = node
}

func (c *LRUCache) removeNode(node *DLinkedNode) {
	node.prev.next = node.next
	node.next.prev = node.prev
}

func (c *LRUCache) moveToHead(node *DLinkedNode) {
	c.removeNode(node)
	c.addToHead(node)
}

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

// 测试代码
func main() {
	cache := Constructor(2)

	cache.Put(1, 1)
	cache.Put(2, 2)
	fmt.Println(cache.Get(1)) // 返回 1
	cache.Put(3, 3)           // 淘汰 key=2
	fmt.Println(cache.Get(2)) // 返回 -1
	cache.Put(4, 4)           // 淘汰 key=1
	fmt.Println(cache.Get(1)) // 返回 -1
	fmt.Println(cache.Get(3)) // 返回 3
	fmt.Println(cache.Get(4)) // 返回 4
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值