左神算法进阶班6_1LFU缓存实现

【题目】

LFU也是一个著名的缓存算法,自行了解之后实现LFU中的set 和 get

要求:两个方法的时间复杂度都为O(1)

【题解】

LFU算法与LRU算法很像

但LRU是最新使用的排在使用频率最前面,也就是LRU是通过使用时间进行排序,

使用时间越新,其使用频率越高,而使用时间越久,其使用频率越低,即当空间满时,被删除的概率最大

而LFU是根据使用次数来算使用频率的,使用次数越多,其使用频率越高,使用次数越少,使用频率越低,当空间满时越容易被删除

同样,使用hash_map表和双向链表进行存储;

hash表存储关键词与双向链表的节点地址

而双向链表的数据存储的是使用次数,每种使用次数仍然是一个双向链表,

当put一个数据,则在使用次数为1的链表节点下的链表中按put顺序依次存储

当每get一个数据,若存在,而在相对应使用次数的链表节点中拿出,放在下一个链表节点下,即使用次数 + 1的节点

记住,在相同的使用次数中,是按照使用时间顺序进行存放的,

所以,当空间满时,首先删除大链表的头中的头,即使用次数为1的节点中的头结点【最早放入】,若使用次数都一样,同样是删除该使用次数节点中链表的 头节点

所以,数据存放于链表尾部,数据删除于链表的头部

还有,当某个链表节点为空,则删除,某个链表节点不存在,则新建

比如,当使用次数为2的节点中无数据,则删出该节点,

当有一个新数据的使用次数为2时,发现不存在使用次数为2的节点,那么就应该新建一个节点

【代码】

  

  1 #pragma once
  2 #include <iostream>
  3 #include <hash_map>
  4
  5 using namespace std;
  6
  7 struct Node//子链表
  8 {
  9     int key;
 10     int val;
 11     int num;
 12     Node* next;
 13     Node* pre;
 14     Node(int a, int b, int n) :key(a), val(b), num(n), next(nullptr), pre(nullptr) {}
 15 };
 16
 17 struct NodeList//主链表
 18 {
 19     int num;
 20     Node* head;//子链表的头节点
 21     Node* tail;//子链表的尾结点
 22     NodeList* next;
 23     NodeList* pre;
 24     NodeList(int a) :num(a), next(nullptr), pre(nullptr)
 25     {
 26         head = new Node(0, 0, a);//新建一个子链表的头结点
 27         tail = head;
 28     }
 29 };
 30
 31 class LFU
 32 {
 33 public:
 34     LFU(int size) :capacity(size) {}
 35     void set(int key, int value);
 36     int get(int key);
 37
 38 private:
 39     void getNode(Node*& p, NodeList*& h);//将节点从子链表中取出
 40     void moveNode(Node*& p, NodeList*& h);//将节点向后移动
 41     void deleteNode(int num, NodeList*& h);//删除子链表
 42     void createNode(Node*p, NodeList*& h);//新建子链表,并插入在主链中
 43     void updataNode(Node*& p, NodeList*& h);//更新函数的使用次数
 44     void deleteData();//容量不足需要删除
 45
 46 private:
 47     int capacity;
 48     NodeList* headList = new NodeList(0);//主链表的头结点
 49     hash_map<int, Node*>dataMap;//key  <——>  真实数据节点地址
 50     hash_map<int, NodeList*>headMap;//次数 <——>  链表头节点地址
 51 };
 52
 53 void LFU::set(int key, int value)
 54 {
 55     if (this->capacity == 0)
 56         return;
 57     if (dataMap.find(key) != dataMap.end())//已经存在
 58     {
 59         Node* p = dataMap[key];//找到数据节点
 60         NodeList* h = headMap[p->num];//找到头链表节点
 61         p->val = value;
 62
 63         updataNode(p, h);//更新数据的使用次数
 64     }
 65     else//如果不存在,则新建
 66     {
 67         if (dataMap.size() >= this->capacity)//容量不足,需要删除数据
 68             deleteData();
 69
 70         Node* p = new Node(key, value, 1);//使用用一次
 71         dataMap[key] = p;//记录
 72
 73         //将新建节点插入使用1次的子链表中
 74         if (headMap.find(1) == headMap.end())//当使用1次的子链表不存在
 75             createNode(p, headList);
 76         else
 77             moveNode(p, headMap[1]);//插入在使用次数在1的子链表中
 78     }
 79 }
 80
 81 int LFU::get(int key)
 82 {
 83     if (dataMap.find(key) == dataMap.end())//数据不存在
 84         return -1;
 85     Node* p = dataMap[key];//找到数据节点
 86     NodeList* h = headMap[p->num];
 87     updataNode(p, h);
 88
 89     return p->val;
 90 }
 91
 92 void LFU::getNode(Node*& p, NodeList*& h)//将节点从子链表中取出
 93 {
 94     p->pre->next = p->next;
 95     if (p->next == nullptr)
 96         h->tail = p->pre;
 97     else
 98         p->next->pre = p->pre;
 99 }
100
101 void LFU::moveNode(Node*& p, NodeList*& q)//将节点向后移动
102 {
103     p->next = q->tail->next;
104     q->tail->next = p;
105     p->pre = q->tail;
106     q->tail = p;
107 }
108
109 void LFU::deleteNode(int num, NodeList*& h)//删除子链表
110 {
111     headMap.erase(num);//从map中删除
112     h->pre->next = h->next;
113     if (h->next != nullptr)
114         h->next->pre = h->pre;
115     delete h;
116     h = nullptr;
117 }
118
119
120 void LFU::createNode(Node*p, NodeList*& h)//新建子链表,并插入在主链中
121 {
122     NodeList* q = new NodeList(p->num);//新建一个子链表
123     headMap[p->num] = q;//保存对应的地址
124
125     moveNode(p, q);////将节点放入子链表中
126
127     //将新子链插入主链表中
128     q->next = h->next;
129     if (h->next != nullptr)
130         h->next->pre = q;
131     h->next = q;
132     q->pre = h;
133 }
134
135 void LFU::updataNode(Node*& p, NodeList*& h)//更新函数的使用次数
136 {
137     int num = p->num;
138     p->num++;//使用次数+1
139
140     //将p从子链表中取出
141     getNode(p, h);
142
143     //将该数据向后面移动
144     if (headMap.find(p->num) == headMap.end())//不存在num+1的节点,那么新建
145         createNode(p, h);
146     else
147         moveNode(p, headMap[p->num]);////将节点放入子链表中
148
149     //如果该子链表为空,将该子链表删除,并从map中删除
150     if (h->head == h->tail)
151         deleteNode(num, h);
152 }
153
154 void LFU::deleteData()//容量不足需要删除
155 {
156     NodeList* p = headList->next;
157     Node* q = p->head->next;//删除子链表排在最前面的数据
158     if (q == p->tail)//要删除的数据就是最后一个数据,则删除该节点和子链表
159         deleteNode(q->num, p);
160     else
161     {
162         p->head->next = q->next;
163         q->next->pre = p->head;
164     }
165     dataMap.erase(q->key);//删除记录
166     delete q;//删除
167     q = nullptr;
168 }
169
170
171 void Test()
172 {
173     LFU* f = new LFU(3);
174     f->set(1, 11);
175     f->set(2, 22);
176     f->set(3, 33);
177     cout << f->get(4) << endl;
178     f->set(4, 44);
179     cout << f->get(1) << endl;
180     cout << f->get(2) << endl;
181     cout << f->get(2) << endl;
182     cout << f->get(2) << endl;
183     cout << f->get(3) << endl;
184     cout << f->get(3) << endl;
185     cout << f->get(4) << endl;
186     cout << f->get(4) << endl;
187     cout << f->get(4) << endl;
188     f->set(5, 55);
189     cout << f->get(3) << endl;
190     cout << f->get(5) << endl;
191
192 }

原文地址:https://www.cnblogs.com/zzw1024/p/11080778.html

时间: 2024-10-08 03:45:07

左神算法进阶班6_1LFU缓存实现的相关文章

左神算法进阶班1_5BFPRT算法

在无序数组中找到第k大的数1)分组,每N个数一组,(一般5个一组)2)每组分别进行排序,组间不排序3)将每个组的中位数拿出来,若偶数,则拿上 / 下中位数, 成立一个一个新数组.4)新数组递归调用BFPRT,则拿到整体的中位数num5)以num来划分整体数组,小于在左,大于在右边,使用[荷兰国旗方法]6)然后根据左右数组的规模,来确定进一步选择左右哪一部分:7)然后选择好后,继续 一:背景介绍在一大堆数中求其前k大或前k小的问题,简称TOP - K问题.而目前解决TOP - K问题最有效的算法即

左神算法进阶班3_1构造数组的MaxTree

题目 一个数组的MaxTree定义: 数组必须没有重复元素 MaxTree是一棵二叉树,数组的每一个值对应一个二叉树节点 包括MaxTree树在内且在其中的每一棵子树上,值最大的节点都是树的头 给定一个没有重复元素的数组arr,写出生成这个数组的MaxTree的函数,要求如果数组长度为N,则时间负责度为O(N).额外空间负责度为O(N). 实现思路 将数组按照大根堆进行排序 然后直接按照大根堆进行构造一颗二叉树即可. 使用单调栈 通过使用单调栈,将数组中中所有数的左右比他大的数记录下来 当某个数

左神算法进阶班5_1求二叉树中最大搜索子树大小

[题目] 给定一棵二叉树的头节点head,请返回最大搜索二叉子树的大小 [题解] 简化问题,想到该问题的解答应该有几种情形 第一种可能: 最大搜索二叉子树在head的左子树 第二种可能: 最大搜索二叉子树在head的右子树 第三种可能: 最大搜索二叉子树为自己:利用左子树的最大值与右子树的最小值 递归左子树,再递归右子树 信息1:左子树中最大搜索二叉树的大小 信息2:右子树中最大搜索二叉树的大小 信息3:左子树最大搜索二叉树的头结点 信息4:右子树最大搜索二叉树的头结点 信息5:左子树上的最大值

左神算法进阶班1_1添加最少字符得到原字符N次

Problem: 给定一个字符串str1,只能往str1的后面添加字符变成str2. 要求1:str2必须包含两个str1,两个str1可以有重合,但是不能以同一个位置开头. 要求2:str2尽量短最终返回str2 举例: str1 = 123,str2 = 123123 时,包含两个str1,且不以相同位置开头,且str2最短. str1 = 123123,str2 = 123123123 时,包含两个str1,且不以相同位置开头,且str2最短. str1 = 111,str2 = 1111

左神算法进阶班5_3求公司的最大活跃度

[题目] 一个公司的上下节关系是一棵多叉树,这个公司要举办晚会,你作为组织者已经摸清了大家的心理: 一个员工的直接上级如果到场,这个员工肯定不会来. 每个员工都有一个活跃度的值,决定谁来你会给这个员工发邀请函,怎么让舞会的气氛最活跃? 返回最大的活跃值. 举例: 给定一个矩阵来表述这种关系 matrix = { 1,6 1,5 1,4 } 这个矩阵的含义是: matrix[0] = { 1 , 6 },表示0这个员工的直接上级为1, 0这个员工自己的活跃度为6 matrix[1] = { 1 ,

左神算法基础班3_13深度拷贝含有随机指针的链表

Problem: 复制含有随机指针节点的链表 [题目] 一种特殊的链表节点类描述如下: public class Node { public int value; public Node next; public Node rand; public Node(int data) { this.value = data; } } Node类中的value是节点值,next指针和正常单链表中next指针的意义 一 样,都指向下一个节点,rand指针是Node类中新增的指针,这个指 针可 能指向链表中

左神算法第八节课:介绍递归和动态规划(汉诺塔问题;打印字符串的全部子序列含空;打印字符串的全排列,无重复排列;母牛数量;递归栈;数组的最小路径和;数组累加和问题,一定条件下最大值问题(01背包))

暴力递归: 1,把问题转化为规模缩小了的同类问题的子问题 2,有明确的不需要继续进行递归的条件(base case) 3,有当得到了子问题的结果之后的决策过程 4,不记录每一个子问题的解 动态规划 1,从暴力递归中来 2,将每一个子问题的解记录下来,避免重复计算 3,把暴力递归的过程,抽象成了状态表达 4,并且存在化简状态表达,使其更加简洁的可能 一:递归 1. 汉诺塔问题 汉诺塔问题(不能大压小,只能小压大),打印n层汉诺塔从最左边移动到最右边的全部过程. 左中右另称为 from.to.hel

左神算法书籍《程序员代码面试指南》——2_07将单向链表按某值划分成左边小、中间相等、右边大的形式

Problem:[题目] 给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整数pivot. 实现一个调整链表的函数,将链表调整为左部分都是值小于 pivot的节点, 中间部分都是值等于pivot的节点,右部分都是值大于 pivot的节点. 除这个要求外,对调整后的节点顺序没有更多的要求. 例如:链表9->0->4->5->1,pivot = 3. 调整后链表可以是1->0->4->9->5, 可以是0->1->9->5-&g

【左神算法课】子数组最大差值小于某阈值,求满足条件的子数组个数

题目描述: 解法思路: 本题其实是滑动窗口的变形.主体思路为: 1.从第一个元素开始依次向后遍历,同时维护两个窗口(由于要同时操作窗口的头部和尾部,故采用双端队列): 最大值窗口(递减),头部永远存最大值 最小值窗口(递增),头部永远存最小值 2.比较两个窗口的头部元素差值,若差值大于阈值,即可跳出内循环. 3.跳出内循环后,检查头部元素是否过期,若过期,则清除. 复杂度: 时间复杂度:O(n),注意虽然是两层循环,但元素只从滑动窗口尾部进,从头部清除,只是顺序扫描了一遍. 空间复杂度:O(n)