左神算法学习日记——LFU(最近最少被使用)

 一个能够使set和get操作的时间复杂度为O(1),敲代码都需要事先将所有可能性考虑清楚
,然后是扣清楚队列的边界,以防出错。

template<class K, class V>
class Node
{
public:
	K key;
	V value;
	int time;//为了能够判断该节点是否被经常使用
	Node<K, V>* up;//为了实现时间复杂度为O(1)的LFU,左神造了一个二维的双端队列,Node是第一维的元素
	Node<K, V>* down;
	Node(K k, V v, int t)
	{
		key = k;
		value = v;
		time = t;
		up = NULL;
		down = NULL;
	}
};

template<class K, class V>
class Nodelist//这是双端队列的第二维的元素,第二维的每个元素记录了被访问了相同次数的key
{
public:
	Node<K, V>* head;
	Node<K, V>* tail;
	Nodelist* next;
	Nodelist<K, V>* last;
	Nodelist() = default;
	Nodelist(Node<K, V>* node)
	{
		head = node;
		tail = node;
		next = NULL;
		last = NULL;
	}
	void addNode(Node<K, V>* node)
	{
		if (!head)
		{
			head = node;
			tail = node;
		}
		node->down = head;
		head = node;
	}
	void deleteNode(Node<K, V>* node)
	{
		if (node == head)
			head = node->down;
		else if (node == tail)
			tail = node->up;
		else
		{
			node->up->down = node->down;
			node->down->up = node->up;
		}
	}
};

template<class K, class V>
class LFU
{
public:
	int Size;
	int s;
	Nodelist<K, V>* Head;
	hash_map<K, Node<K, V>*> nodehash;//记录所有key所对应的地址
	hash_map<Node<K, V>*, Nodelist<K, V>*> nlhash;//记录了结点所对应的一维双端队列
	LFU() = default;
	LFU(int S)
	{
		Size = S;
	}
	bool ifremlist(Node<K, V>* node)
	{
		Nodelist<K, V>* curlist = nlhash[node];
		if (curlist->head == curlist->tail)//如果当前结点所在队列的大小为1,则从LFU中删除该队列
		{
			if (Head == curlist)//如果该队列为头队列则头指针需要指向新的队列
			{
				Nodelist<K, V>* temp = Head;
				Head = Head->next;
				delete temp;
				return true;
			}
			curlist->last->next = curlist->next;//当前队列的前一个队列需要指向新的next
			if (curlist->next)//如果后一个存在,则连上前一个
				curlist->next->last = curlist->last;
			delete curlist;
			return true;
		}
		return false;
	}
	void move(Node<K, V>* node)
	{
		Nodelist<K, V>* curlist = nlhash[node];//为了移动结点我们需要得知当前结点所在一维双端队列
		Nodelist<K, V>* pre = curlist->last;
		if (!ifremlist(node))
		{
			pre = curlist;
			curlist->deleteNode(node);
		}
		Nodelist<K, V>* beh = pre->next;
		if (!beh)//左神这里单独列出来,减少了判断的次数
		{
			Nodelist<K, V>* Newlist = new Nodelist<K, V>();
			Newlist->addNode(node);
			if (pre)
				pre->next = Newlist;
			else
				Head = Newlist;
			Newlist->last = pre;
			nlhash[node] = Newlist;
		}
		else{
			if (beh->head->time == node->time)//如果后一个一维队列的次数就是当前结点的次数,那就加入到该链表中。
			{
				beh->addNode(node);
				nlhash[node] = beh;
			}
			else//如果不是结点的次数或者后一个队列为空则创建一个队列并将节点放进去
			{
				Nodelist<K, V>* Newlist = new Nodelist<K, V>();
				Newlist->addNode(node);
				if (pre)//有可能结点原来所在的队列被删除掉了且该队列的头队列,pre可能为空
					pre->next = Newlist;
				else
					Head = Newlist;
				Newlist->last = pre;
				Newlist->next = beh;//如果beh存在在next指向beh否则为空
				beh->last = Newlist;
				nlhash[node] = Newlist;
			}
		}
	}
	void set(K key, V value)//通过这个二维双端队列进行set
	{
		if (nodehash.find(key) != nodehash.end())//判断当前key是否存在,我们需要一个hash_set(),实现复杂度为O(1)的查找操作
		{
			Node<K, V>* Newnode = nodehash[key];
			Newnode->time++;
			Newnode->value = value;
			move(Newnode);//次数加一后我们需要将这个key移到对应次数的一维双端队列中
			return;
		}
		if (s == Size)
		{
			Node<K, V>* temp = Head->tail;
			
			if(!ifremlist(temp))
				Head->tail = Head->tail->up;
			nlhash.erase(temp);//删除结点后更新两个hash表
			nodehash.erase(temp->key);
			delete temp;
			s--;
		}
		Node<K, V>* Newnode = new Node<K, V>(key, value, 1);//如果不存在则创建一个time为1的结点
		if (!Head)
			Head = new Nodelist<K,V>(Newnode);
		else
		{
			if (Head->head->time == 1)//头队列的次数为1则插入
			{
				Head->head->up = Newnode;
				Head->head = Newnode;
			}
			else
			{
				Nodelist<K, V>* Newlist = new Nodelist<K, V>();//不存在则新建一个队列
				Newlist->addNode(Newnode);
				Newlist->next = Head;
				Head->last = Newlist;
				Head = Newlist;
			}
		}
		nodehash.insert({ key, Newnode });//添加结点后,更新两个hash表
		nlhash.insert({ Newnode, Head });
		s++;
	}
	V get(K key)
	{
                if(nodehash.find(key)==nodehash.end())
                   return 0;
		nodehash[key]->time++;//改变结点的次数和所在的队列
		move(nodehash[key]);
		return nodehash[key]->value;
	}
};

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值