我看到的许多实现都具有运行时复杂度为
O(log(n))
。这意味着,当缓存大小为
n
时,将元素插入/从缓存中删除所需的时间是对数级别的。这些实现通常使用
最小堆
来维护元素的使用频率。堆的根包含具有最低频率的元素,并且可以在
O(1)
的时间内访问。但是,为了维护堆属性,我们必须每次在堆内使用元素(并增加频率)时将其移动到正确的位置,或者在将新元素插入缓存时(因此将其放入堆中)。
但是,当我们使用元素作为键维护
哈希表
(Java)或
unordered_map
(C++)时,运行时复杂度可以降至
O(1)
。此外,我们需要两种类型的列表:
频率列表
和
元素列表
。
元素列表
包含具有相同频率的元素,而
频率列表
包含
元素列表
。
frequency list
1 3 6 7
a k y x
c l z
m n
在这个例子中,我们看到有一个包含4个元素列表(4个元素列表
)的频率列表
。元素列表1
包含元素(a,c,m)
,元素列表3
包含元素(k, l, n)
等等。
现在,当我们使用元素y
时,我们必须增加它的频率并将其放入下一个列表中。因为频率为6的元素列表变为空,所以我们删除它。结果如下:
frequency list
1 3 7
a k y
c l x
m n z
我们将元素y放在“elements list” 7的开头。以后当我们需要从列表中删除元素时,我们将从末尾开始(先是
z
,然后是
x
,最后是
y
)。
现在,当我们使用元素
n
时,我们必须增加它的频率并将其放入新列表中,频率为4:
frequency list
1 3 4 7
a k n y
c l x
m z
希望这个想法很清楚。我现在提供我的LFU缓存的C++实现,并将稍后添加Java实现。
该类仅具有2个公共方法,
void set(key k, value v)
和
bool get(key k, value &v)
。在get方法中,当找到元素时,要检索的值将按引用设置,此时方法返回true。当未找到元素时,该方法返回false。
#include<unordered_map>
#include<list>
using namespace std;
typedef unsigned uint;
template<typename K, typename V = K>
struct Entry
{
K key;
V value;
};
template<typename K, typename V = K>
class LFUCache
{
typedef typename list<typename Entry<K, V>> ElementList;
typedef typename list <pair <uint, ElementList>> FrequencyList;
private:
unordered_map <K, pair<typename FrequencyList::iterator, typename ElementList::iterator>> cacheMap;
FrequencyList elements;
uint maxSize;
uint curSize;
void incrementFrequency(pair<typename FrequencyList::iterator, typename ElementList::iterator> p) {
if (p.first == prev(elements.end())) {
elements.push_back({ p.first->first + 1, { {p.second->key, p.second->value} } });
cacheMap[p.second->key] = { prev(elements.end()), prev(elements.end())->second.begin() };
}
else {
auto pos = next(p.first);
if (p.first->first + 1 == pos->first)
pos->second.push_front({ p.second->key, p.second->value });
else
pos = elements.insert(pos, { p.first->first + 1 , {{p.second->key, p.second->value}} });
cacheMap[p.second->key] = { pos, pos->second.begin() };
}
if (p.first->second.size() == 1)
elements.erase(p.first);
else
p.first->second.erase(p.second);
}
void eraseOldElement() {
if (elements.size() > 0) {
auto key = prev(elements.begin()->second.end())->key;
if (elements.begin()->second.size() < 2)
elements.erase(elements.begin());
else
elements.begin()->second.erase(prev(elements.begin()->second.end()));
cacheMap.erase(key);
curSize--;
}
}
public:
LFUCache(uint size) {
if (size > 0)
maxSize = size;
else
maxSize = 10;
curSize = 0;
}
void set(K key, V value) {
auto entry = cacheMap.find(key);
if (entry == cacheMap.end()) {
if (curSize == maxSize)
eraseOldElement();
if (elements.begin() == elements.end()) {
elements.push_front({ 1, { {key, value} } });
}
else if (elements.begin()->first == 1) {
elements.begin()->second.push_front({ key,value });
}
else {
elements.push_front({ 1, { {key, value} } });
}
cacheMap.insert({ key, {elements.begin(), elements.begin()->second.begin()} });
curSize++;
}
else {
entry->second.second->value = value;
incrementFrequency(entry->second);
}
}
bool get(K key, V &value) {
auto entry = cacheMap.find(key);
if (entry == cacheMap.end())
return false;
value = entry->second.second->value;
incrementFrequency(entry->second);
return true;
}
};
以下是使用示例:
int main()
{
LFUCache<int>cache(3);
cache.set(1, 1);
cache.set(2, 2);
cache.set(3, 3);
cache.set(2, 4);
rc = cache.get(1, r);
assert(rc);
assert(r == 1);
cache.set(4, 5);
rc = cache.get(3, r);
assert(!rc);
rc = cache.get(4, r);
assert(rc);
assert(r == 5);
LFUCache<int, string>cache2(2);
cache2.set(1, "one");
cache2.set(2, "two");
string val;
rc = cache2.get(1, val);
if (rc)
assert(val == "one");
else
assert(false);
cache2.set(3, "three");
rc = cache2.get(2, val);
assert(rc == false);
rc = cache2.get(3, val);
assert(rc);
assert(val == "three");
}