我见过的许多实现都具有运行时复杂性O(log(n))。这意味着,当缓存大小为n 时,将元素插入/删除缓存所需的时间是对数的。这样的实现通常使用min heap 来维护元素的使用频率。堆的根包含频率最低的元素,可以在O(1)时间访问。但是为了维护堆属性,我们必须移动一个元素,每次在堆内部使用它(并且频率增加),将它放置到适当的位置,或者当我们必须将新元素插入缓存时(等等将其放入堆中)。
但是运行时复杂度可以降低到O(1),当我们维护hashmap (Java) 或unordered_map (C++) 并以元素为键。此外,我们需要两种列表,frequency list 和 elements lists。 elements lists 包含具有相同频率的元素,frequency list 包含element lists。
frequency list
1 3 6 7
a k y x
c l z
m n
在示例中,我们看到 frequency list 有 4 个元素 (4 elements lists)。元素列表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())) {
//frequency list contains single list with some frequency, create new list with incremented frequency (p.first->first + 1)
elements.push_back({ p.first->first + 1, { {p.second->key, p.second->value} } });
// erase and insert the key with new iterator pair
cacheMap[p.second->key] = { prev(elements.end()), prev(elements.end())->second.begin() };
}
else {
// there exist element(s) with higher frequency
auto pos = next(p.first);
if (p.first->first + 1 == pos->first)
// same frequency in the next list, add the element in the begin
pos->second.push_front({ p.second->key, p.second->value });
else
// insert new list before next list
pos = elements.insert(pos, { p.first->first + 1 , {{p.second->key, p.second->value}} });
// update cachMap iterators
cacheMap[p.second->key] = { pos, pos->second.begin() };
}
// if element list with old frequency contained this singe element, erase the list from frequency list
if (p.first->second.size() == 1)
elements.erase(p.first);
else
// erase only the element with updated frequency from the old list
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 of size 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);
// evict old element, in this case 3
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"); // evict 2
rc = cache2.get(2, val);
assert(rc == false);
rc = cache2.get(3, val);
assert(rc);
assert(val == "three");
}