📅  最后修改于: 2023-12-03 14:44:04.419000             🧑  作者: Mango
LRU,全称为 Least Recently Used(最近最少使用)算法,是一种常见的缓存淘汰算法。其思想是根据数据访问的时间顺序,将最近最少使用的数据淘汰掉,从而保留最有用数据的算法。
LRU 缓存淘汰算法的核心思想是维护一张缓存清单,将最近被访问的数据插入到缓存清单的队首,当缓存清单满了之后再加入新数据时,淘汰掉缓存队尾的数据即可。
LRU 算法的核心是有序链表,链表头部是最常访问的数据,链表尾部是最近最少使用的数据。每次新访问一个数据时,如果数据在链表中存在,则将该数据移到链表头部;如果不存在,则在链表头部插入该数据。每次淘汰数据时,删除链表尾部的数据即可。
而 LRU 近似 (二次机会算法) 则是对 LRU 算法进行了优化,"LRU 近似(二次机会算法)"本质上是把节点使用的频率用两个 bit 来进行记录,第一个 bit 充当一个访问位,记录节点是否被访问过,每次被访问后该 bit 会被置为 1;第二个 bit 充当一个过期位,记录节点是否过期,初始值为 0。所谓过期,是指双链表的元素被访问而且已经经过了一段时间,所以需要将元素标记为过期,等待被淘汰。
LRU 近似 (二次机会算法) 的实现基于双向链表和哈希表,使用双向链表可以保证在插入、查找和删除操作上的高效性,而哈希表可以迅速定位某个节点是否存在。
算法初始化时,哈希表和双链表都是空的。每次访问节点时,先在哈希表中查找该节点是否存在。如果存在,则将该节点从链表中移动到链表头部,并将“访问位”置为 1;如果不存在,则在链表头部插入该节点并将其“访问位”置为 1。如果链表已满,那么需要选择最长时间没有被访问过的节点进行删除,如果该节点的“过期位”为 0,则将其置为 1;若该节点已经“过期位”为 1,则将其删除。
以下为示例代码实现,其中 hash_map 为 STL 提供的哈希表实现。
#define MAXSIZE 5
typedef struct TListNode
{
int key; // 节点 key 值
int value; // 节点 value 值
int bit_access; // 节点第一个 bit(访问位)
int bit_expire; // 节点第二个 bit(过期位)
struct TListNode *prev; // 前驱节点
struct TListNode *next; // 后继节点
}ListNode;
class LRUCache
{
private:
int curSize; // 当前已使用的缓存空间
int maxSize; // 缓存空间的最大容量
ListNode *head; // 头结点
ListNode *tail; // 尾结点
unordered_map<int, ListNode*> hash_map; // 哈希表
void removeNodeFromList(ListNode* node) // 从链表中删除某个节点
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
void insertNodeAtHead(ListNode* node) // 将节点插入链表头部
{
node->next = head->next;
node->prev = head;
head->next->prev = node;
head->next = node;
}
public:
LRUCache(int capacity)
{
curSize = 0;
maxSize = capacity;
head = new ListNode;
tail = new ListNode;
head->next = tail;
tail->prev = head;
}
int get(int key)
{
if(hash_map.find(key) == hash_map.end())
{
return -1; // 如果哈希表中不存在该节点,则返回 -1
}
else
{
ListNode* node = hash_map[key];
node->bit_access = 1; // 将节点访问位置为 1
removeNodeFromList(node);
insertNodeAtHead(node);
return node->value;
}
}
void put(int key, int value)
{
if(hash_map.find(key) == hash_map.end())
{
ListNode* newNode = new ListNode;
newNode->key = key;
newNode->value = value;
newNode->bit_access = 1; // 将节点访问位置为 1
newNode->bit_expire = 0; // 将节点过期位置为 0
hash_map[key] = newNode; // 在哈希表中插入该节点
insertNodeAtHead(newNode); // 将节点插入链表头部
++curSize; // 更新当前已使用的缓存空间
if(curSize > maxSize) // 如果当前已使用的缓存空间超过了最大容量
{
ListNode* lastNode = tail->prev;
while(lastNode != head) // 寻找最长时间没有被访问过的节点
{
if(lastNode->bit_access == 1) // 如果该节点的访问位为 1,则将其访问位置为 0
{
lastNode->bit_access = 0;
}
else if(lastNode->bit_expire == 0) // 如果该节点的过期位为 0,则将其过期位置为 1,表示待删除
{
lastNode->bit_expire = 1;
break;
}
else // 如果该节点的过期位为 1,则删除该节点
{
ListNode* nodeToRemove = lastNode;
lastNode = nodeToRemove->prev;
removeNodeFromList(nodeToRemove);
hash_map.erase(nodeToRemove->key);
delete nodeToRemove;
--curSize;
}
}
}
}
else
{
ListNode* node = hash_map[key];
node->value = value;
node->bit_access = 1; // 将节点访问位置为 1
removeNodeFromList(node);
insertNodeAtHead(node);
}
}
};
以上为 LRU 近似 (二次机会算法) 的代码实现,算法的时间复杂度为 O(1),空间复杂度为 O(n)。