LRU的实现
运用你所掌握的数据结构,设计和实现一个LRU (最近最少使用) 缓存机制 。它应该支持以下操作: 获取数据 get
和 写入数据 put
。
获取数据 get(key)
- 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value)
- 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
思路:
LRU实现采用双向链表 + hash_map 来进行实现。这里采用双向链表的原因是:如果采用普通的单链表,则删除节点的时候需要从表头开始遍历查找,效率为O(n),采用双向链表可以直接改变节点的前驱的指针指向进行删除达到O(1)的效率。使用map来保存节点的key、value值便于能在O(logN)的时间查找元素,对应get操作。
代码:
#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
#include<iostream>
#include<hash_map>
#include<algorithm>
using namespace std;
struct CacheNode {
int key;
int value;
CacheNode *pre;
CacheNode *next;
CacheNode(int k, int v) :key(k), value(v), pre(nullptr), next(nullptr) {}
};
class LRUCache {
public:
LRUCache(int capacity) :size(capacity), head(nullptr), tail(nullptr) {}
int get(int key);
void set(int key,int value);
void remove(CacheNode *node);
void SetHead(CacheNode *node);
CacheNode *GetHead();
private:
int size;//最大缓存数
CacheNode *head, *tail;
hash_map<int, CacheNode *>map;//int是关键字
};
int LRUCache::get(int key)
{
auto it = map.find(key);//先在hash表中查一下是否有该关键字
//存在该关键字
if (it != map.end())
{
CacheNode *node = it->second;
//要获取的关键字正好位于双向链表的头节点
if (node == GetHead())
return node->value;
else
{
remove(node);
SetHead(node);
}
return node->value;
}
else
return -1;
}
void LRUCache::set(int key, int value)
{
auto it = map.find(key);//先在hash表中查一下是否有该关键字
//以前已经存在该关键字,需要修改值
if (it != map.end())
{
CacheNode *node = it->second;
//要获取的关键字正好位于双向链表的头节点
if (node == GetHead())
{
node->value = value;
return;
}
node->value = value;
remove(node);
SetHead(node);
}
else
{
CacheNode *node = new CacheNode(key, value);
if (map.size() == size)
{
auto it = map.find(tail->key);
remove(tail);
map.erase(it);
}
SetHead(node);
map[key] = node;
}
}
void LRUCache::remove(CacheNode *node)
{
if (node->pre != nullptr)
node->pre->next = node->next;
else
head = node->next;
if (node->next != nullptr)
node->next->pre = node->pre;
else
tail = node->pre;
}
void LRUCache::SetHead(CacheNode *node)
{
if (head == nullptr)
{
head = node;
tail = node;
}
else
{
head->pre = node;
node->next = head;
head = node;
}
}
CacheNode* LRUCache::GetHead()
{
return head;
}
int main(void)
{
LRUCache *lruCache = new LRUCache(2);
lruCache->set(1, 10);
lruCache->set(2, 11);
lruCache->get(1);
lruCache->set(3, 12);
cout << lruCache->get(1) << endl;
cout << lruCache->get(2) << endl;
system("pause");
return 0;
}
LFU的实现
设计并实现最不经常使用(LFU)缓存的数据结构。它应该支持以下操作:get
和 put
。
get(key)
- 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。put(key, value)
- 如果键不存在,请设置或插入值。当缓存达到其容量时,它应该在插入新项目之前,使最不经常使用的项目无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,最近最少使用的键将被去除。
进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?
思路:
LRU实现采用2个双向链表 + 2个hash_map 来进行实现。一个双向链表用来链接各个频率的链表,另一个链表用来链接相同访问频率的各个记录。这两个链表是嵌套关系。
两个hash_map分别是存放关键字和对应的node,以及node所对应的频率链。
代码:
#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS
#include<iostream>
#include<hash_map>
#include<algorithm>
using namespace std;
class Node {
public:
Node(int k, int v, int t) :key(k), value(v), times(t), pre(nullptr), next(nullptr) {}
public:
int key;//关键字
int value;//值
int times;//访问的次数
Node *pre;//前一个节点
Node *next;//后一个节点
};
class NodeList {
public:
NodeList(Node *node);
//每次新加入节点都是从头部加入,所以对于
//访问次数相同的关键字,删除的是链表尾部的那个
void addNodeFromHead(Node *newHead);
bool IsEmpty() { return head == nullptr; }
void deleteNode(Node *node);
public:
Node *head;
Node *tail;
NodeList *pre;
NodeList *next;
};
NodeList::NodeList(Node *node)
{
head = node;
tail = node;
pre = nullptr;
next = nullptr;
}
void NodeList::addNodeFromHead(Node *newHead)
{
newHead->next = head;
head->pre = newHead;
head = newHead;
}
void NodeList::deleteNode(Node *node)
{
//删掉这个节点之后,这个NodeList为空了
if (head == tail)
{
head = nullptr;
tail = nullptr;
}
//删掉这个节点之后,链表中还至少有一个节点
else
{
if (node == head)
{
head = node->next;
head->pre = nullptr;
}
else if (node == tail)
{
tail = node->pre;
tail->next = nullptr;
}
else
{
node->pre->next = node->next;
node->next->pre = node->pre;
}
}
node->next = nullptr;
node->pre = nullptr;
}
class LFUCache {
public:
LFUCache(int c);
//加入一条记录
void set(int key, int value);
//oldNodeList是Node原来属于的那个NodeList,当访问数加一之后需要找到下一个NodeList
void move(Node *node, NodeList* oldNodeList);
//把一个node从lodeList里面delete之后,然后调用此
//方法进行判断是不是连整个NodeList不要了,如果此NodeList不在了返回true
bool modifyHeadList(NodeList *nodeList);
//返回关键字对应的记录
int get(int key);
private:
int capacity;//缓冲区容量
int size;//目前缓冲区中的元素的数量
hash_map<int, Node *> records;//关键字对应的节点构成一个map
hash_map<Node *, NodeList *> heads;//每个节点对应一个NodeList,每个NodeList都是访问次数相同的关键字
NodeList *headList;//整个NodeList中的第一个
};
LFUCache::LFUCache(int c)
{
capacity = c;
size = 0;
headList = nullptr;
}
void LFUCache::set(int key, int value)
{
//说明缓冲区中有这个关键字
auto it = records.find(key);
if (it != records.end())
{
Node *node = it->second;
node->value = value;//设置新的值
node->times++;//访问次数++
NodeList *curNodeList = heads[node];//找到node对应的那个NodeList
move(node, curNodeList);
}
else//缓冲区之前没有这个关键字
{
if (size == capacity)//容量已经到达上限,需要删除一个node
{
Node *node = headList->tail;//删除的node是第一个NodeList的tail
headList->deleteNode(node);
modifyHeadList(headList);//这个NodeList是否需要调整
records.erase(node->key);
heads.erase(node);
size--;
}
Node *node = new Node(key, value, 1);//新建一个节点,访问次数为1
if (headList == nullptr)
headList = new NodeList(node);
else
{
if (headList->head->times == node->times)//如果当前的第一个NodeList正好就是node所对应的访问次数
headList->addNodeFromHead(node);//使用头插法进行加入
else//第一个NodeList所对应的访问次数不是1,需要新建一个NodeList
{
NodeList *newList = new NodeList(node);
newList->next = headList;
headList->pre = newList;
headList = newList;//更换新的headList
}
}
records[key] = node;
heads[node] = headList;
size++;
}
}
//oldNodeList是Node原来属于的那个NodeList,当访问数加一之后需要找到下一个NodeList
void LFUCache::move(Node *node, NodeList* oldNodeList)
{
oldNodeList->deleteNode(node);//因为node对应的关键字的访问次数加一,所以更换NodeList
//如果删除一个节点之后oldNodeList不存在了,那么preList就是oldNodeList的前一个
NodeList *preList = modifyHeadList(oldNodeList) ? oldNodeList->pre : oldNodeList;
NodeList *nextList = oldNodeList->next;
if (nextList == nullptr)//oldNodeList没有链表了,那么需要新建一个存放node
{
NodeList *newList = new NodeList(node);
if (preList != nullptr)
preList->next = newList;
newList->pre = preList;
if (headList == nullptr)
headList = newList;
heads[node] = newList;
}
else//oldNodeList有下一个节点
{
if (nextList->head->times == node->times)//正好访问次数相等
{
nextList->addNodeFromHead(node);
heads[node] = nextList;
}
else
{
NodeList *newList = new NodeList(node);
if (preList != nullptr)
preList->next = newList;
newList->pre = preList;
newList->next = nextList;
nextList->pre = newList;
if (headList == nextList)
headList = newList;
heads[node] = newList;
}
}
}
//把一个node从lodeList里面delete之后,然后调用下面这个
//方法进行判断是不是连整个链表不要了
bool LFUCache::modifyHeadList(NodeList *nodeList)
{
//拿掉一个节点之后,nodeList为空
if (nodeList->IsEmpty())
{
if (headList == nodeList)
{
headList = nodeList->next;
if (headList != nullptr)
headList->pre = nullptr;
}
else
{
nodeList->pre->next = nodeList->next;
if (nodeList->next != nullptr)
nodeList->next->pre = nodeList->pre;
}
return true;
}
return false;
}
int LFUCache::get(int key)
{
auto it = records.find(key);
if (it == records.end())
return -1;
Node *node = records[key];
node->times++;
NodeList *curNodeList = heads[node];
move(node, curNodeList);
return node->value;
}
int main(void)
{
LFUCache *lfuCache = new LFUCache(2);
lfuCache->set(1, 10);
lfuCache->set(2, 11);
lfuCache->get(1);
lfuCache->get(2);
lfuCache->get(1);
lfuCache->get(2);
lfuCache->set(3, 12);
cout << lfuCache->get(1) << endl;
cout << lfuCache->get(2) << endl;
system("pause");
return 0;
}