一、题目描述
二、解题思路
先来看一下什么是
缓存机制,假设只给一个进程分配三个页框,当发生缺页的时候,
必须选择一个页面将其换出其所在的页框,这个被选择的页面,将是在现有页框中最久没有被使用过的那个。就拿下面这张图为例:
比如我们现在处于第六步:要调入
号页面,那么应该从现有页面0、2、1中选择一个调出。由于在
号页面前面的序列是
,而现有页面是
,那么就找
这三个数字中在序列中最靠前的那个:第三个数字
。
为此,我们可以维护一个序列,这个序列保存着分配的页框内的现存页面的访问顺序,其长度就是分配到的页框数:
- 序列不满(其
没有达到分配的页框数
- 当前访问页面在序列内
- 找到该页面在序列中的位置
- 把该位置的元素挪移到序列尾
- 当前访问页面不在序列内
- 直接在序列末尾添加当前访问的页框
- 当前访问页面在序列内
- 序列满
- 当前访问页面在序列内
- 将该页面挪移到序列末尾
- 并更新该页面对应的值
- 当前访问页面不在序列内
- 由上面的分析我们其实可以发现,任意时刻序列的顺序就是访问的时间顺序,越靠前其访问得越早
- 删掉序列开头的元素
- 在序列末尾插入当前访问的序列
- 完成页面的调入
- 当前访问页面在序列内
这里还有两个地方有疑问
- 怎么确定页面是否在序列中,如果在,怎么确定其位置
- 怎么完成页面号与页面值的映射
对于第一个问题,当然可以每次都遍历序列,然后找到结论,但是由于时间复杂度太大,因为运行超时,无法通过最后一个(第
个测试用例)。
对于第二个问题,解决映射问题完成
和
的对应,最好使用
。
针对第一个问题进行改进,不应该采用顺序结构,比如数组或者
之类,因为如果需要挪移元素将耗费大量的时间。所以应该采用找到位置后可以直接链接的结构,也就是双向链表
。
针对第二个问题,
的
和
都是什么?考虑这样一种情况,如果
和
分别是页号和页面的值,那么即使根据映射从页号得到了页面值,也还是需要在
中找到对应
的那个页面并进行操作,时间复杂度还是没有降低。
所以想要根据
的
直接找到序列中对应的位置,必须将
的
直接映射成
的迭代器,指向要挪移的那个元素在
内的位置。所以
_
应该设计成unordered_map<int, list<pair<int, int>::iterator>
,真正完成
与
的工作,由
来做。
知道了思路,来整理下代码逻辑
-
操作
- 如果 _ 没有找到与 对应的迭代器,直接返回
- 如果存在
对应的迭代器
- 从 _ 和 里把这个元素抹除
- 更新后再放回到 的末尾
- 重新建立 _ 中关于 的映射,注意此时映射的迭代器应该是 末尾
-
操作:首先调用
,
一下
- 如果
到元素
- 这一定会导致 内顺序的改变,于是交给 的任务变成了更新 末尾元素值
- 如果没有
到元素
- 如果
未满
- 在 末尾插入元素,并在 _ 内建立起 和迭代器的对应
- 如果
已满
- 说明此时应该从 内选择开头的那个旧的页面调出内存,换入新的页面
- 记录下要换出的页面的
- 抹掉 _ 中和 对应的元素
- 抹掉 开头的元素
- 在 末尾插入新的页面
- 在
中建立起
和
末尾迭代器的映射
注意在 操作的时候有一个大坑:抹掉 对应元素的之前,一定要先保存一个拷贝,否则从被抹掉的位置得到的迭代器将指向未知的位置引发程序崩溃。
做完这些,我们可以确保从 _ 中根据 取得的任意一个迭代器指向的都是要进行操作的 的位置,降低了时间复杂度。
- 如果
未满
- 如果
到元素
三、解题代码
class LRUCache
{
private:
unordered_map<int, list<pair<int, int>>::iterator> ump;
list<pair<int, int>> Lst;
int MaxSize;
int HowMuch;
public:
LRUCache(int capacity)
{
HowMuch = 0;
MaxSize = capacity;
}
int get(int key)
{
if (MaxSize <= 0)
return -1;
auto it = ump.find(key);
int ret = -1;
if (it != ump.end())
{ //找到了
ret = it->second->second; //val
auto tmp = it->second;
ump.erase(ump.find(key));
Lst.erase(tmp);
Lst.push_back(pair<int, int>(key, ret));
ump.insert(make_pair(key, --Lst.end()));
}
return ret;
}
void put(int key, int value)
{
if (MaxSize <= 0)
return;
if (get(key) != -1)
{ //找到了
Lst.rbegin()->second = value;
return;
}
else
{
if (HowMuch < MaxSize)
{
Lst.push_back(make_pair(key, value));
ump.insert(make_pair(key, --Lst.end()));
HowMuch++;
return;
}
else if (HowMuch == MaxSize)
{
auto rm_it = Lst.begin();
ump.erase(ump.find(rm_it->first));
Lst.erase(Lst.begin());
Lst.push_back(make_pair(key, value));
ump.insert(make_pair(key, --Lst.end()));
return;
}
}
}
};
四、运行结果
五、总结
Lst.begin()
和Lst.rbegin()
不是同一类型的迭代器Lst.begin()
是正常的 迭代器Lst.rbegin()
是反向 迭代器
- 有 结构,如果 也有的话,就不用费劲设计 了