页面置换算法--LFU算法实现-O时间复杂度
Posted master-dragon
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了页面置换算法--LFU算法实现-O时间复杂度相关的知识,希望对你有一定的参考价值。
LFU: least frequently used (LFU) page-replacement algorithm
若有读者看到,希望在理解思路后,自己多敲几遍(收获会较大)
leetcode题目地址
https://leetcode.com/problems/lfu-cache/?tab=Description
题目描述
Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LFUCache cache = new LFUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.get(3); // returns 3.
cache.put(4, 4); // evicts key 1.
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
题目讨论,各种解决方案
https://leetcode.com/problems/lfu-cache/?tab=Solutions
ac1
数据结构设计
java版本
// 双向链表的节点
class Node
public int key, val;
public Node next, prev;
public Node(int k, int v)
this.key = k;
this.val = v;
class DoubleList
private Node head, tail; // 头尾虚节点
private int size; // 链表元素数
public DoubleList()
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
size = 0;
// O(1)
// 头插
public void addFirst(Node x)
x.next = head.next;
x.prev = head;
head.next.prev = x;
head.next = x;
size++;
public Node getFirst()
if (tail.prev == head)
return null;
return head.next;
public Node getLast()
if (tail.prev == head)
return null;
return tail.prev;
// O(1)
// 删除链表中的 x 节点(x 一定存在)
public void remove(Node x)
x.prev.next = x.next;
x.next.prev = x.prev;
size--;
// O(1)
// 删除链表中最后一个节点,并返回该节点
public Node removeLast()
if (tail.prev == head)
return null;
Node last = tail.prev;
remove(last);
return last;
// 返回链表长度
public int size() return size;
public boolean isEmpty() return size == 0;
class LFUCache
Map<Integer, DoubleList> freMap;
Map<Integer, Integer> keyFreMap;
Map<Integer, Node> keyNodeMap;
int cap;
int totalSize;
int minFre;
public LFUCache(int capacity)
cap = capacity;
freMap = new HashMap<>();
keyFreMap = new HashMap<>();
keyNodeMap = new HashMap<>();
minFre = 0;
totalSize = 0;
// 删除原来就存在的一个key
public void removeExistKey(int key)
int fre = keyFreMap.get(key);
DoubleList doubleList = freMap.get(fre);
// 删除老的
Node oldNode = keyNodeMap.get(key);
doubleList.remove(oldNode);
keyNodeMap.remove(key);
keyFreMap.remove(key);
if(doubleList.isEmpty())
freMap.remove(fre);
if(minFre == fre)
minFre ++;
totalSize --;
// 添加新节点,fre为新节点的fre, node为新Node
public void addNewNode(int fre, Node node)
// 设置新的
DoubleList doubleList = freMap.getOrDefault(fre, new DoubleList());
doubleList.addFirst(node);
keyNodeMap.put(node.key, doubleList.getFirst());
freMap.put(fre, doubleList);
keyFreMap.put(node.key, fre);
totalSize ++;
public int get(int key)
if(!keyFreMap.containsKey(key))
return -1;
int fre = keyFreMap.get(key);
Node oldNode = keyNodeMap.get(key);
int retVal = oldNode.val;
// 删除旧的
removeExistKey(key);
// 设置新的
addNewNode(fre + 1, oldNode);
return retVal;
public void put(int key, int value)
if(cap == 0)
return;
if(!keyFreMap.containsKey(key)) // put新元素
if(totalSize == cap)
// 需要删除
DoubleList doubleList = freMap.get(minFre);
Node removeNode = doubleList.getLast();
removeExistKey(removeNode.key);
Node newNode = new Node(key, value);
// 添加新节点,其频率是1
addNewNode(1, newNode);
minFre = 1;
else
int fre = keyFreMap.get(key);
// 删除旧的
removeExistKey(key);
// 设置新的
Node newNode = new Node(key, value);
addNewNode(fre + 1, newNode);
ac代码
class LFUCache
public:
int size;
int cap;
int minfreq;
map<int,pair<int,int>> m;//key to pair<value,freq>
map<int,list<int>::iterator> mIter;//key to list location , key在 list中的位置一个iterator
map<int,list<int>> fm;//freq to list , list存放的是所有的key, 最后的key是最近访问过的,头部的是最近没有访问的(淘汰)
public:
LFUCache(int capacity)
cap = capacity;
size = 0;
int get(int key)
if(m.count(key) == 0)
return -1;
//key 频率加1,删除原来其在fm中的位置,插入到新的位置
fm[m[key].second].erase(mIter[key]);
m[key].second ++;
fm[m[key].second].push_back(key);
mIter[key] = --fm[m[key].second].end(); // 当前key所在的位置
if(fm[minfreq].size() == 0) //上面的步骤处理后,可能最小频率已经删除了数据,所以需要判断
minfreq ++;
return m[key].first;
void put(int key, int value)
if(cap <= 0)
return ;
/*
调用成员方法get
如果不存在,返回-1;
如果已经存在,那么就会修改频数,删除旧的位置,添加到新的位置,但是值仍然是原来的,需要修改
*/
int storeValue = get(key);
if(storeValue != -1)
m[key].first = value;
return; // 直接返回
// 不存在的情况, 已经满了,需要删除频率最小,最近都没有访问过的那个key
if(size >= cap)
m.erase(fm[minfreq].front());
mIter.erase(fm[minfreq].front());
fm[minfreq].pop_front();
size --;
pair<int, int> pr(value, 1);
m[key] = pr;
fm[1].push_back(key);
mIter[key] = --fm[1].end();
minfreq = 1;
size ++;
;
ac2
参考
https://discuss.leetcode.com/topic/78833/c-89ms-beats-99-8-using-unordered_map-list-of-list
数据结构设计
ac代码
class LFUCache
public:
struct info
int val;
list<pair<int, list<int>>>::iterator it_pair;
list<int>::iterator it_key;
;
LFUCache(int capacity)
cap_ = capacity;
int get(int key)
auto it = map_.find(key);
if (it == map_.end())
return -1;
else
visit(it, key);
return it->second.val;
void put(int key, int value)
auto it = map_.find(key);
// 已经存在,需要更新value,改变其频数,在map等中的信息都要更改
if (it != map_.end())
visit(it, key);
it->second.val = value;
else
if (cap_ == 0) return;
// del
if (map_.size() == cap_)
auto it = list_.front().second.begin();
map_.erase(*it);
list_.front().second.erase(it);
if (list_.front().second.size() == 0)
list_.erase(list_.begin());
// insert
if (list_.empty() || list_.front().first != 1)
list<int> li;
li.push_back(key);
pair<int,list<int>> pr(1, li);
list_.push_front(pr);
//list_.push_front(1, key);
else
// 最小的频数list中插入一个key, 插入到最后面表示是最近访问到的
list_.front().second.push_back(key);
info in;
in.val = value;
in.it_pair = list_.begin();
in.it_key = std::prev(list_.front().second.end());
map_[key] = in;
//map_[key] = value, list_.begin(), std::prev(list_.front().second.end());
protected:
void visit(unordered_map<int, info>::iterator it, int key)
auto it_pair = it->second.it_pair; // 在list中的哪个频数上面
auto it_key = it->second.it_key; // 在map中的位置
int count = it_pair->first + 1; // 频数加1
it_pair->second.erase(it_key); // 删除该key,如果对应频数没有key了, list_要将这个记录直接删除,并把it指向下一个记录
if (it_pair->second.size() == 0)
it_pair = list_.erase(it_pair);
else
std::advance(it_pair, 1); // it_pair往前移动
if (it_pair == list_.end() || it_pair->first != count)
// list_没有该新的频数,就构造出来,插入到对应的位置
list<int> li;
li.push_back(key);
pair<int,list<int>> pr(count, li);
it_pair = list_.insert(it_pair, pr);
//it_pair = list_.insert(it_pair, count, key);
else
it_pair->second.push_back(key);
// 更新info信息
it->second.it_pair = it_pair;
it->second.it_key = std::prev(it_pair->second.end());
int cap_;
list<pair<int, list<int>>> list_;
unordered_map<int, info> map_;
;
同样的ac2思想,仿照ac1代码形式改写,还是可以ac,不过ac时间更长(可能是C++ stl相关操作的影响)
class LFUCache
public:
struct Info
int val;
list<pair<int, list<int>>>::iterator it_pair;
list<int>::iterator it_key;
;
int size;
int cap;
list<pair<int, list<int>>> liFreq; // freq keys
unordered_map<int, Info> mp; //key->Info
public:
LFUCache(int capacity)
size = 0;
cap = capacity;
int get(int key)
if(mp.count(key) == 0)
return -1;
Info oldInfo = mp[key];
list<pair<int, list<int>>>::iterator old_it_pair = oldInfo.it_pair;
list<int>::iterator old_it_key = oldInfo.it_key;;
old_it_pair->second.erase(old_it_key); // 删除旧的
int newFreqNum = old_it_pair->first + 1; // 新的的频率
list<pair<int, list<int>>>::iterator new_it_pair; // key需要到的新的liFreq位置
if((int)old_it_pair->second.size() == 0)
new_it_pair = liFreq.erase(old_it_pair); // 删除该频数
else
new_it_pair = std::next(old_it_pair);
// 是否需要构建新的频数,然后key总是插入到最后面
if(new_it_pair == liFreq.end() || new_it_pair->first != newFreqNum)
// liFreq 没有该新的频数,就构造出来,插入到对应的位置
list<int> li;
li.push_back(key);
pair<int,list<int>> pr(newFreqNum, li);
new_it_pair = liFreq.insert(new_it_pair, pr); // 位置之前插入,并返回插入后,元素所处的位置
else
new_it_pair->second.push_back(key);
mp[key].it_pair = new_it_pair;
mp[key].it_key = std::prev(new_it_pair->second.end());
return mp[key].val;
void put(int key, int value)
if(cap <= 0)
return;
int storeVal = get(key);
if(storeVal !=-1)
mp[key].val = value;
return;
if(size == cap)
auto it = liFreq.front().second.begin();
int outKey = *it;
mp.erase(outKey);
liFreq.front().second.erase(it); // 删除key
if((int)liFreq.front().second.size() == 0) // 若删除key后 该频数为空了,那么该频数也可以删除掉了
liFreq.erase(liFreq.begin());
size --;
size ++;
// insert
if (liFreq.empty() || liFreq.front().first != 1)
list<int> li;
li.push_back(key);
pair<int,list<int>> pr(1, li);
liFreq.push_front(pr);
else
// 最小的频数list中插入一个key, 插入到最后面表示是最近访问到的
liFreq.front().second.push_back(key);
Info in;
in.val = value;
in.it_pair = liFreq.begin();
in.it_key = std::prev(liFreq.front().second.end());
mp[key] = in;
;
以上是关于页面置换算法--LFU算法实现-O时间复杂度的主要内容,如果未能解决你的问题,请参考以下文章