LRU(最近最久未使用算法)
思路:
这里用到了两个数据结构,一个是list,另一个是unordeed_map
我们的思路也很简单,在map中以key为键,值得话我们取相对应下list存储key的iterator,为什么呢?主要是我们可以利用map的O(1)的查找,如果能找得到,就表示List是存在这样的key的。 这里,我们以list的队尾为最近最久未使用的元素节点,也就是说队头的一定是刚刚使用过的节点。
用一个结构体作为list的元素节点,node里面必须存储key和value !
为什么要把key存储下来呢?那是因为我们在map删除的时候,是要用到key的,通过key来得到list下的iterator,这样我们才能删除成功。
map元素的值其实就是list对应下key的iterator,这个很重要,理解了这个,这道题就完成了。
struct node{
int key,value;
node(int a,int b):key(a),value(b){}
};
class LRUCache {
public:
LRUCache(int capacity) {
_size = capacity;
lt.clear();
mp.clear();
}
int get(int key) {
if(mp.find(key)==mp.end()){ //如果没有找到,则没有
return -1;
}
lt.splice(lt.begin(),lt,mp[key]); //这个内置函数后面会讲到
mp[key] = lt.begin(); //进行map元素的更新
return mp[key]->value; //返回查找的值
}
void put(int key, int value) {
if(mp.find(key)==mp.end()){ //没有这个值
if(lt.size()==_size){ //进行插入时需要判断list存储的容量是否超标,是的话就要进行页面换出的决策
mp.erase(lt.back().key); //在map删除换出元素的记录
lt.pop_back(); //在list删除换出元素的记录
}
lt.push_front(node(key,value)); //在队头添加,表示刚刚用到的节点
mp[key] = lt.begin(); //存储key 以及对应的iterator
}
else{ //找到这个值,那么只需要进行更新即可
//最重要的一点就是要把刚刚用到的节点放到队头上,这样才符合LRU算法思想,还有记得也要更新 key下的map元素的值。
mp[key]->value = value;
lt.splice(lt.begin(),lt,mp[key]);
mp[key] = lt.begin();
}
}
private:
list<node> lt;
unordered_map<int,list<node>::iterator> mp;
int _size;
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
### LFU(最近最少未使用算法)
版本一:
来自力扣的官方解释
代码:
struct node{
int cnt,time,key,value;
node(int a,int b,int c,int d):cnt(a),time(b),key(c),value(d){}
};
struct cmp{
bool operator()(const node& a,const node& b)const{
if(a.cnt==b.cnt){
return a.time<b.time;
}
return a.cnt<b.cnt;
}
};
class LFUCache {
public:
LFUCache(int capacity) {
_size = capacity;
_time = 0;
mp.clear();
st.clear();
}
int get(int key) {
if(_size==0) return -1;
auto it = mp.find(key);
if(it==mp.end()) return -1;
node op = it->second;
st.erase(st.find(op));
++op.cnt;
op.time = ++_time;
st.insert(op);
it->second = op;
return op.value;
}
void put(int key, int value) {
if(_size==0) return ;
auto it = mp.find(key);
if(it==mp.end()){
if(_size==st.size()){
mp.erase(st.begin()->key);
st.erase(st.begin());
}
node op = node(1,++_time,key,value);
st.insert(op);
mp.insert(make_pair(key,op));
}
else{
node bk = it->second;
st.erase(bk);
bk.cnt += 1;
bk.time = ++_time;
bk.value = value;
it->second = bk;
st.insert(bk);
}
}
private:
unordered_map<int,node> mp;
set<node,cmp> st;
int _time;
int _size;
};
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache* obj = new LFUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
版本二:
思路:
- 这里主要是用到了两个unordered_map数据结构,其底层实现就是hash_map,主要可以近似的认为存取查找操作O(1)的时间复杂度。
- key_map的类型是<key,list< node >::iterator>,这里的作用跟版本的map是一样的,主要是看看是否存在key元素,至于为什么存储的是iterator呢?那是因为我们可以再利用preq_map(其类型是< preq ,list< ndoe >>)去寻找list链表存储的key对应的位置,方便删除操作。
- preq_map数据结构里为什么存储的是list链表呢?版本一是怎么确认最近最少使用的页面?我们是不是用到了一个时间戳,这样在排序的时候就可以当频率一样时看时间戳即可。那么在这里呢?怎么确定?
在这里,我们就是根据preq(在LFU类里的那个preq)这个变量,这样我们就知道当前最少次数的是哪些了,前提是我们必须给这些页面一个良好的管理,把出现相同preq次数一样的页面,用一个list链表进行存储,那怎么确定相同次数下哪个页面是最早的呢?其实很简单,我们只需要定一个规范,把刚访问的页面放在list链表的头,尾巴不就是最早的吗?(也就是要被淘汰的页面)- 剩下的就是细节处理,具体看代码。
代码:
struct node{
int key,value;
int preq; //频率,即次数
node(int a,int b,int c):key(a),value(b),preq(c){}
};
class LFUCache {
public:
LFUCache(int capacity) {
minpreq = 0;
_size = capacity;
key_map.clear();
preq_map.clear();
}
int get(int key) {
if(_size==0) return -1;
auto it = key_map.find(key);
if(it==key_map.end()) return -1;
int value = it->second->value;
int preq = it->second->preq;
preq_map[preq].erase(it->second);
if(preq_map[preq].size()==0 ){
preq_map.erase(preq);
if(minpreq==preq){
minpreq += 1;
}
}
preq_map[preq+1].push_front(node(key,value,preq+1));
key_map[key] = preq_map[preq+1].begin();
return value;
}
void put(int key, int value) {
if(_size==0) return;
auto it = key_map.find(key); //先看看是否有这个key存在
if(it==key_map.end()){ //不存在
if(key_map.size() == _size){ //如果超过了内存的页面数量,那么就要进行置换
node& it = preq_map[minpreq].back(); //这一步的操作就是为了能在key_map中删除对应key的记录
key_map.erase(it.key);
preq_map[minpreq].pop_back(); //通过私有变量minpreq进行preq_map上对应list的删除应该要被换出的页面
if(preq_map[minpreq].empty()){ //如果该minpreq变量下的list刚好被删除到为空,这里最好就把minpreq对应的list的链表删除
preq_map.erase(minpreq);
}
}
minpreq = 1; //很明显,当前的minpreq等于1
preq_map[1].push_front(node(key,value,1)); //分别在两个map添加记录
key_map[key] = preq_map[1].begin();
}else{ //存在
auto _iterator = it->second;
int preq = _iterator->preq;
preq_map[preq].erase(_iterator); //这里的步骤就是先把原先的preq下的那个旧记录删除,然后再添加新元素(其实也不能说是新元素,应该是新的记录,只要是为了更新preq频率)
if(preq_map[preq].empty()){
preq_map.erase(preq);
if(minpreq==preq){
minpreq += 1;
}
}
preq_map[preq+1].push_front(node(key,value,preq+1));
key_map[key] = preq_map[preq+1].begin();
}
}
private:
int minpreq;
int _size;
unordered_map<int,list<node>::iterator> key_map;
unordered_map<int,list<node> > preq_map;
};
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache* obj = new LFUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/