C++实现缓存算法LRU和LFU

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wk_bjut_edu_cn/article/details/84834686

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;
}

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/84834686