算法就像搭乐高:带你手撸 LFU 算法 :: labuladong的算法小抄 #1064
Replies: 11 comments 2 replies
-
FK表,是否可以是哈希链表?LinkedHashMap也可以满足我们 3.3,3.4,3.5 |
Beta Was this translation helpful? Give feedback.
-
其他语言没有java的LinkedHashSet,参照LRU的LinkedHashMap的自定义实现可以自己实现 |
Beta Was this translation helpful? Give feedback.
-
这个是否可以 定义后面的 freq2Key的 数据结构 为 HashMap<Integer,int[]> |
Beta Was this translation helpful? Give feedback.
-
@zhongweiLeex 有想法可以尝试写一下解法 |
Beta Was this translation helpful? Give feedback.
-
看了东哥的LRU想着LFU也能手写出来,没想到花了一个多小时哈哈。 class LFUCache {
LinedList list;
public LFUCache(int capacity) {
list = new LinedList(capacity);
}
public int get(int key) {
return list.get(key);
}
public void put(int key, int value) {
list.add(key, value);
}
}
class LinedList {
Map<Integer, Node> map = new HashMap<>();
TreeMap<Integer, Node> fluentMap = new TreeMap<>();
Node head;
Node tail;//越靠近tail频率越高,如果频率一样的话,那么最近被访问的会更靠近
int capacity;
int size;
public LinedList(int c) {
head = new Node();
tail = new Node();
head.next = tail;
head.fluent = 0;
tail.prev = head;
tail.fluent = Integer.MAX_VALUE;
capacity = c;
size = 0;
// fluentMap.put(0, head);
}
public int get(int k) {
if (!map.containsKey(k)) {
return -1;
}
Node node = map.get(k);
fluentAdjust(node);
return node.value;
}
private void fluentAdjust(Node node) {
//更新我以前的频率或者删除已经不在存在的频率
if (node.next.fluent != node.fluent && node.prev.fluent != node.fluent) {//我是唯一一个这个频率的,现在我要走了,删除
fluentMap.remove(node.fluent);
} else if (node.next.fluent != node.fluent && node.prev.fluent == node.fluent) {//我是最后一个这个频率的
fluentMap.put(node.fluent, node.prev);
}
node.fluent++;
Node pre=head;
Map.Entry<Integer, Node> nodeEntry = fluentMap.floorEntry(node.fluent);
if(nodeEntry!=null){
pre=nodeEntry.getValue();
}
//把以前的接上来
remove(node);
Node next = pre.next;
pre.next=node;
node.prev=pre;
next.prev=node;
node.next=next;
fluentMap.put(node.fluent,node);
// Node cur=node,pre=node.prev;
// while(node.fluent<=cur.next.fluent){
// pre=cur;
// cur=cur.next;
// }
//删除node
// node.prev.next = node.next;
// node.next.prev = node.prev;
//链接node到正确的位置
// node.prev = pre;
// node.next = cur;
// pre.next = node;
// cur.prev = node;
}
public void remove(Node node){
Node pre = node.prev;
Node next = node.next;
pre.next=next;
next.prev=pre;
}
public void add(int k, int v) {
if(capacity==0){
return;
}
if (map.containsKey(k)) {
//更新节点
Node node = map.get(k);
node.value = v;
fluentAdjust(node);
} else {
if (size >= capacity) {
//移出最前面的
if (head.next != tail) {
int key = head.next.key;
// map.remove(key);
Node node = map.remove(key);
if (node.fluent != node.next.fluent) {
fluentMap.remove(node.fluent);
}
head.next.next.prev = head;
head.next = head.next.next;
}
}
Node node = new Node(k, v);
Node cur = fluentMap.get(1);
if(cur==null){
cur=head;
}
Node next=cur.next;
cur.next=node;
node.prev=cur;
node.next=next;
next.prev=node;
//因为新加入的频率是1
// Node cur = head.next;
// Node pre = head;
// while (cur.fluent <= 1) {
// pre = cur;
// cur = cur.next;
// }
// pre.next = node;
// node.prev = pre;
// cur.prev = node;
// node.next = cur;
fluentMap.put(1,node);
map.put(k,node);
size++;
}
}
}
class Node {
int key;
int value;
int fluent;
Node next;
Node prev;
public Node() {
}
public Node(int k, int v) {
key = k;
value = v;
fluent = 1;
}
} |
Beta Was this translation helpful? Give feedback.
-
贴一个C++版本,用了两个哈希链表,感觉比较容易理解和记住 struct Node {
int key;
int val;
int freq;
Node(int _key, int _val, int _freq): key(_key), val(_val), freq(_freq) {}
};
class LFUCache {
unordered_map<int, list<Node>::iterator> key_table; // 每个键key保存着对应的链表节点的地址,注意这里必须保存地址,否则对链表节点的修改需要手动更新哈希表
unordered_map<int, list<Node>> freq_table; // 每个freq对应一个链表,表头是最新的元素,表尾是最旧的元素
int capacity;
int min_freq = 0;
public:
LFUCache(int capacity) {
this->capacity = capacity;
key_table.clear();
freq_table.clear();
}
// 用于超出容量时,删除最小使用频率的最不常用的节点,也就是其链表的尾节点
void removeLast(int freq) {
Node last = freq_table[freq].back(); // 找到这个freq对应的链表list_node中的表尾元素
key_table.erase(last.key); // 将其从key_table中删除
freq_table[freq].pop_back(); // 将其从list_node删除
if (freq_table[freq].size() == 0) { // 如果这条链表为空了,则直接删除这个键表对,并更新min_freq
freq_table.erase(freq);
if (min_freq == freq) min_freq++;
}
}
// 用于当调用get或put当键存在时,将其从freq的链表中删除然后放到freq + 1的链表表头, 此函数中除了freq对应的链表节点全部被删除的情况外,min_freq不需要更新,因为freq只增加不减少
void moveToHead(int key) {
// 首先将节点从freq的链表中删除
list<Node>::iterator iter = key_table[key];
int val = iter->val;
int freq = iter->freq;
freq_table[freq].erase(iter);
if (freq_table[freq].size() == 0) { // 如果这条链表为空了,则直接删除这个键表对,并更新min_freq
freq_table.erase(freq);
if (min_freq == freq) min_freq++;
}
// 然后将该节点加入到freq + 1的链表头部
// 由于freq_table[freq].erase(iter)会直接将iter指针从链表中删除,需要创建一个新的节点放到freq+1链表表头,并更新key_table中的key对应的节点地址
freq_table[freq + 1].push_front(Node(key, val, freq + 1));
key_table[key] = freq_table[freq + 1].begin();
}
int get(int key) {
if (!key_table.count(key) || capacity == 0) return -1;
moveToHead(key);
return key_table[key]->val;
}
void put(int key, int value) {
if (capacity == 0) return;
// 如果这个键值对存在,则直接修改并更新freq
if (key_table.count(key)) {
auto iter = key_table[key];
iter->val = value;
moveToHead(key);
return;
}
// 如果键不存在
if (key_table.size() == capacity) {
removeLast(min_freq);
}
freq_table[1].push_front(Node(key, value, 1));
key_table[key] = freq_table[1].begin();
min_freq = 1;
}
}; |
Beta Was this translation helpful? Give feedback.
-
照着写了个Python版本,用OrderedDict替代LinkedHashSet from collections import OrderedDict
class LFUCache:
def __init__(self, capacity):
self.key2val = {} # key 到 val 的映射,我们后文称为 KV 表
self.key2freq = {} # key 到 freq 的映射,我们后文称为 KF 表
self.freq2key = {} # freq 到 key 列表的映射,我们后文称为 FK 表
self.min_freq = 0 # 记录最小的频次
self.capacity = capacity # 记录 LFU 缓存的最大容量
def get(self, key: int) -> int:
if key not in self.key2val.keys():
return -1
# 增加 key 对应的 freq
self.increase_freq(key)
return self.key2val[key]
def put(self, key: int, val: int):
if self.capacity <= 0:
return
# 若 key 已存在,修改对应的 val 即可
if key in self.key2val.keys():
self.key2val[key] = val
self.increase_freq(key)
return
# key不存在,需要插入,容量已满的话需要淘汰一个freq最小的key
if self.capacity <= self.key2val.__len__():
self.remove_minfreqkey(key)
# 插入 key 和 val,对应的 freq 为 1
self.key2val[key] = val
self.key2freq[key] = 1
if 1 not in self.freq2key.keys():
self.freq2key[1] = OrderedDict()
self.freq2key[1][key] = None
# 插入新 key 后最小的 freq 肯定是 1
self.min_freq = 1
def increase_freq(self, key):
freq = self.key2freq[key]
self.key2freq[key] += 1
# 将 key 从 freq 对应的列表中删除
self.freq2key[freq].pop(key)
# 如果 freq 对应的列表空了,移除这个 freq
if freq + 1 not in self.freq2key.keys():
self.freq2key[freq + 1] = OrderedDict()
self.freq2key[freq + 1][key] = None
# 如果 freq 对应的列表空了,移除这个 freq
if len(self.freq2key[freq]) == 0:
self.freq2key.pop(freq)
# 如果这个 freq 恰好是 minFreq,更新 minFreq
if self.min_freq == freq:
self.min_freq += 1
def remove_minfreqkey(self, key):
# req 最小的 key 列表
key_dict = self.freq2key[self.min_freq]
# 其中最先被插入的那个 key 就是该被淘汰的 key
delete_key, _ = key_dict.popitem(last=False) # last=True:LIFO;last=False:FIFO
if len(key_dict) == 0:
self.freq2key.pop(self.min_freq)
# 问:这里需要更新 minFreq 的值吗?
self.key2val.pop(delete_key)
self.key2freq.pop(delete_key) |
Beta Was this translation helpful? Give feedback.
-
C++ copy ,通过 map 、list 和迭代器实现哈希链表: class LFUCache {
public:
unordered_map<int, int> keyToVal; // 存储 key-value
unordered_map<int, int> keyToFreq; // 存储 key-freq(计数值)
unordered_map<int, list<int>> freqToKeys; // 存储 freq-keys(list) 一对多的关系,一个计数值对应多个 key
unordered_map<int, list<int>::iterator> keyToFKNode; // 存储 key - key 在 FK 中的位置,keys(list) 的迭代器,表示 FK[key] 这条链表上的一个节点位置
int cap;
int minFreq;
LFUCache(int capacity) : cap(capacity), minFreq(0) { }
int get(int key) {
if(!keyToVal.count(key)) {
return -1;
}
increaseFreq(key);
return keyToVal[key];
}
void put(int key, int value) {
if(cap <= 0) return;
if(keyToVal.count(key)) {
keyToVal[key] = value;
increaseFreq(key);
return;
}
if(cap <= keyToVal.size()) {
removeMinFreqKey();
}
keyToVal[key] = value;
keyToFreq[key] = 1;
if(!freqToKeys.count(1)) {
freqToKeys[1] = list<int>();
}
freqToKeys[1].push_back(key);
keyToFKNode[key] = --freqToKeys[1].end();
minFreq = 1;
}
void removeMinFreqKey() {
int OldestKeyOfMinFreq = freqToKeys[minFreq].front();
freqToKeys[minFreq].pop_front();
keyToFKNode.erase(OldestKeyOfMinFreq);
if(freqToKeys[minFreq].empty()) {
freqToKeys.erase(minFreq);
}
keyToVal.erase(OldestKeyOfMinFreq);
keyToFreq.erase(OldestKeyOfMinFreq);
}
void increaseFreq(int key) {
// 获取 key 对应的 freq
int freq = keyToFreq[key];
// 更新 KF
keyToFreq[key] = freq + 1;
// 更新 FKS,删除旧的 freq
freqToKeys[freq].erase(keyToFKNode[key]);
if(freqToKeys[freq].empty()) {
freqToKeys.erase(freq);
// freq 对应的 keys 空了
if(freq == minFreq) {
minFreq++; // 如果 minFreq == freq,增加 minFreq (不用担心增加后的 minFreq 不存在,因为它至少会有一个当前这个 key 对应)
}
}
// 更新 FKS,添加新的 freq
if(!freqToKeys.count(freq + 1)) {
freqToKeys[freq + 1] = list<int>();
}
freqToKeys[freq + 1].push_back(key);
keyToFKNode[key] = --freqToKeys[freq + 1].end();
}
}; |
Beta Was this translation helpful? Give feedback.
-
手动实现LinkHashMap 贴一个ruby版本 class Node
attr_accessor :key, :val, :prev, :next
def initialize(key,val)
@key = key
@val = val
end
end
# 双向链表
class DoubleList
attr_accessor :head, :tail, :size
def initialize
@head = Node.new(nil, nil)
@tail = Node.new(nil, nil)
@head.next = @tail
@tail.prev = @head
@size = 0
end
# 添加-加到尾部
def add(node)
# node自己指针
node.prev = self.tail.prev
node.next = self.tail
# node前结点指针
node.prev.next = node
# 尾部指针
self.tail.prev = node
@size += 1
end
# 删除
def remove(node)
node.prev.next = node.next
node.next.prev = node.prev
@size -= 1
end
end
# 链表hash
class LinkListHashMap
attr_accessor :map, :list
def initialize
@map = {} # key -> node
@list = DoubleList.new
end
# 添加
def add(key, val=nil)
node = Node.new(key, val)
@list.add(node)
@map[key] = node
end
# 更新
def update(key,val=nil)
node = @map[key]
node.val = val
end
# 删除
def remove(key)
node = @map[key]
@list.remove(node)
@map.delete(key).val
end
# 获取
def get(key)
@map[key].val
end
# 删除第一个
def remove_first
first = @list.head.next
remove(first.key) if first != @list.tail
end
# 第一个key(便于LFU使用)
def first_key
first = @list.head.next
first.key
end
# 是否存在
def exists?(key)
@map.keys.include?(key)
end
def size
@list.size
end
end
class LFUCache
attr_accessor :key_to_val, :key_to_freq, :freq_to_keys, :capacity, :min_freq
def initialize(capacity)
@key_to_val = {} # key -> val
@key_to_freq = {} # key -> freq 频率
@freq_to_keys = {} # 频率 -> keys列表(这里用LinkListHashMap实现)
@capacity = capacity # 容量
@min_freq = 0 # 当前最小频率
end
def get(key)
if key_exists?(key)
increase_freq(key)
@key_to_val[key]
else
-1
end
end
def put(key, val)
if key_exists?(key)
# 更新KV
@key_to_val[key] = val
increase_freq(key)
else
if self.capacity <= size
remove_min_freq_key
end
# 更新kV
@key_to_val[key] = val
# 更新KF
@key_to_freq[key] = 1
# 更新FK
add_or_update_fk(1,key)
# 更新min_freq
self.min_freq = 1
end
end
private
# key是否存在
def key_exists?(key)
@key_to_val.keys.include?(key)
end
# 增加某个key使用频次
def increase_freq(key)
freq = @key_to_freq[key]
# 更新KF频次
@key_to_freq[key] = freq + 1
# 更新FK
key_list = @freq_to_keys[freq] # 频率键列表
key_list.remove(key) # 删除旧频率对应list中key
if key_list.size == 0 # 如果删除key后,list为空
@freq_to_keys.delete(freq) # 删除旧频率
if freq == @min_freq # 刚好等于最低频率(只有当为key_list为空时需要做此判断,不空没影响)
@min_freq += 1
end
end
# 添加新的频率(freq+1)到FK
add_or_update_fk(freq+1,key)
end
# 添加or更新fk
def add_or_update_fk(freq,key)
if @freq_to_keys.keys.include?(freq)
key_list = @freq_to_keys[freq]
key_list.add(key)
else
list = LinkListHashMap.new
list.add(key)
@freq_to_keys[freq] = list
end
end
# 移除最小频率key
def remove_min_freq_key
key_list = @freq_to_keys[self.min_freq]
delete_key = key_list.first_key
# 更新kV
self.key_to_val.delete(delete_key)
# 更新kF
self.key_to_freq.delete(delete_key)
# 更新FK
key_list.remove(delete_key)
if key_list.size == 0 # 删除key后空了
@freq_to_keys.delete(self.min_freq)
# 需要更新min_freq?
# PS: 由于只有在新增的时候才会触发remove_min_freq_key,而新增key,min_freq 为1,
# 所以没必要更新,即使更新那么只能遍历,时间复杂度也就上去了
end
end
# 当前容量
def size
@key_to_val.keys.size
end
end |
Beta Was this translation helpful? Give feedback.
-
C++的官方版本 class LFUCache {
// key: frequency, value: list of original key-value pairs that have the same frequency.
unordered_map<int, list<pair<int, int>>> frequencies;
// key: original key, value: pair of frequency and the iterator corresponding key int the
// frequencies map's list.
unordered_map<int, pair<int, list<pair<int, int>>::iterator>> cache;
int capacity;
int minf;
void insert(int key, int frequency, int value) {
frequencies[frequency].push_back({key, value});
cache[key] = {frequency, --frequencies[frequency].end()};
}
public:
LFUCache(int capacity) : capacity(capacity), minf(0) {}
int get(int key) {
const auto it = cache.find(key);
if (it == cache.end()) {
return -1;
}
const int f = it->second.first;
const auto iter = it->second.second;
const pair<int, int> kv = *iter;
frequencies[f].erase(iter);
if (frequencies[f].empty() && minf == f) {
++minf;
}
insert(key, f + 1, kv.second);
return kv.second;
}
void put(int key, int value) {
if (capacity <= 0) {
return;
}
const auto it = cache.find(key);
if (it != cache.end()) {
it->second.second->second = value;
get(key);
return;
}
if (capacity == cache.size()) {
cache.erase(frequencies[minf].front().first);
frequencies[minf].pop_front();
}
minf = 1;
insert(key, 1, value);
}
}; |
Beta Was this translation helpful? Give feedback.
-
c++重新整理了一下思路 class LFUCache { public:
private:
}; |
Beta Was this translation helpful? Give feedback.
-
文章链接点这里:算法就像搭乐高:带你手撸 LFU 算法
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions