zoukankan      html  css  js  c++  java
  • LRU 缓存机制 设计和实现一个 LRU(最近最少使用)缓存数据结构

    题目:LRU 缓存机制 设计和实现一个 LRU(最近最少使用)缓存数据结构,使它应该支持一下操作:get 和 put。 get(key) - 如果 key 存在于缓存中,则获取 key 的 value(总是正数),否则返回 -1。 put(key,value) - 如果 key 不存在,请设置或插入 value。当缓存达到其容量时,它应该在插入新项目之前使最近最少使用的项目作废。

    参考答案

    进阶:

    你是否可以在 O(1) 时间复杂度内完成这两种操作?

    示例:

    LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
    
    cache.put(1, 1);
    cache.put(2, 2);
    cache.get(1);       // 返回  1
    cache.put(3, 3);    // 该操作会使得密钥 2 作废
    cache.get(2);       // 返回 -1 (未找到)
    cache.put(4, 4);    // 该操作会使得密钥 1 作废
    cache.get(1);       // 返回 -1 (未找到)
    cache.get(3);       // 返回  3
    cache.get(4);       // 返回  4
    

    思路解析

    这道题是让我们实现一个 LRU 缓存器,LRU是Least Recently Used的简写,就是最近最少使用的意思。

    这个缓存器主要有两个成员函数,get和put。

    其中 get 函数是通过输入 key 来获得 value,如果成功获得后,这对 (key, value) 升至缓存器中最常用的位置(顶部),如果 key 不存在,则返回 -1 。

    而 put 函数是插入一对新的 (key, value),如果原缓存器中有该 key,则需要先删除掉原有的,将新的插入到缓存器的顶部。如果不存在,则直接插入到顶部。

    若加入新的值后缓存器超过了容量,则需要删掉一个最不常用的值,也就是底部的值。

    具体实现时我们需要三个私有变量,cap , l 和 m,其中 cap 是缓存器的容量大小,l 是保存缓存器内容的列表,m 是 HashMap,保存关键值 key 和缓存器各项的迭代器之间映射,方便我们以 O(1) 的时间内找到目标项。

    然后我们再来看 get 和 put 如何实现。

    其中,get 相对简单些,我们在 m 中查找给定的key,若不存在直接返回 -1;如果存在则将此项移到顶部。

    对于 put ,我们也是现在 m 中查找给定的 key,如果存在就删掉原有项,并在顶部插入新来项,然后判断是否溢出,若溢出则删掉底部项(最不常用项)。

    动画演示

    动画录制少录了几秒,见谅:

    Made by Jun Chen

    参考代码

    python版本的:

    class LRUCache(object):
        def __init__(self, capacity):
        """
        :type capacity: int
        """
        self.cache = {}
        self.keys = []
        self.capacity = capacity
        
        def visit_key(self, key):
            if key in self.keys:
                self.keys.remove(key)
            self.keys.append(key)
        
        def elim_key(self):
            key = self.keys[0]
            self.keys = self.keys[1:]
            del self.cache[key]
            
        def get(self, key):
            """
            :type key: int
            :rtype: int
            """
            if not key in self.cache:
                return -1
            self.visit_key(key)
            return self.cache[key]
        
        def put(self, key, value):
            """
            :type key: int
            :type value: int
            :rtype: void
            """
            if not key in self.cache:
            if len(self.keys) == self.capacity:
            self.elim_key()
            self.cache[key] = value
            self.visit_key(key)
    
    def main():
        s =
        [["put","put","get","put","get","put","get","get","get"],[[1,1],[2,2],[1],[3,3],[2],[
        4,4],[1],[3],[4]]]
        obj = LRUCache(2)
        l=[]
        for i,c in enumerate(s[0]):
            if(c == "get"):
                l.append(obj.get(s[1][i][0]))
            else:
                obj.put(s[1][i][0], s[1][i][1])
        print(l)
    
    if __name__ == "__main__":
        main()
    
    

    c++版本的:

    class LRUCache{
        public:
            LRUCache(int capacity) {
                cap = capacity;
            }
            
            int get(int key) {
                auto it = m.find(key);
                if (it == m.end()) return -1;
                l.splice(l.begin(), l, it->second);
                return it->second->second;
            }
            
            void set(int key, int value) {
                auto it = m.find(key);
                if (it != m.end()) l.erase(it->second);
                l.push_front(make_pair(key, value));
                m[key] = l.begin();
                if (m.size() > cap) {
                    int k = l.rbegin()->first;
                    l.pop_back();
                    m.erase(k);
                }
            }
        
    private:
        int cap;
        list<pair<int, int>> l;
        unordered_map<int, list<pair<int, int>>::iterator> m;
    };
    

    Java代码:

    package question;
    
    import java.util.*;
    
    /**
     * 哈希表加双向链表。
     *
     * 每次新增节点时,往链表头部放入。需要删除时,删除链表尾节点。
     *
     * get()和put()的时间复杂度均是O(1)。空间复杂度是O(n),其中n为缓存的键数。
     *
     * 执行用时:141ms,击败79.91%。消耗内存:55.7MB,击败96.89%。
     */
    class LRUCache1 {
        private class Node {
            private int key;
            private int value;
            private Node pre;
            private Node next;
    
            public Node() {
            }
    
            public Node(int key, int value) {
                this.key = key;
                this.value = value;
            }
        }
    
        private Node dummyHead = new Node();
        private Node dummyTail = new Node();
        private int capacity;
        private int size;
        private HashMap<Integer, Node> hashMap = new HashMap<>();
    
        //将节点添加到虚拟头节点之后
        private void add(Node node) {
            Node originHead = dummyHead.next;
            dummyHead.next = node;
            node.pre = dummyHead;
            node.next = originHead;
            originHead.pre = node;
        }
    
        //删除某个节点
        private void del(Node node) {
            Node preNode = node.pre;
            Node nextNode = node.next;
            preNode.next = nextNode;
            nextNode.pre = preNode;
            node.pre = null;
            node.next = null;
        }
    
        public LRUCache1(int capacity) {
            dummyHead.next = dummyTail;
            dummyTail.pre = dummyHead;
            this.capacity = capacity;
            size = 0;
        }
    
        public int get(int key) {
            Node node = hashMap.get(key);
            if (null == node) {
                return -1;
            }
            del(node);
            add(node);
            return node.value;
        }
    
        public void put(int key, int value) {
            Node node = hashMap.get(key);
            if (null != node) {
                node.value = value;
                del(node);
                add(node);
            } else {
                if (size < capacity) {
                    size++;
                } else {
                    //删除链表尾节点
                    Node delNode = dummyTail.pre;
                    hashMap.remove(delNode.key);
                    del(delNode);
                }
                Node newNode = new Node(key, value);
                add(newNode);
                hashMap.put(key, newNode);
            }
        }
    }
    
    
    
    或
    
    
    package question;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * 自定义一个雷LRULinedHashMap继承自LinkedHashMap,并重写其removeEldestEntry方法。
     *
     * get()和put()的时间复杂度均是O(1)。空间复杂度是O(n),其中n为缓存的键数。
     *
     * 执行用时:144ms,击败74.73%。消耗内存:62.3MB,击败67.95%。
     */
    public class LRUCache2 {
    
        private int capacity;
        private LRULinkedHashMap<Integer, Integer> lruLinkedHashMap = new LRULinkedHashMap<>();
    
        private class LRULinkedHashMap<K, V> extends LinkedHashMap<K, V> {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                if (size() > capacity) {
                    return true;
                } else {
                    return false;
                }
            }
        }
    
        public LRUCache2(int capacity) {
            this.capacity = capacity;
        }
    
        public int get(int key) {
            Integer value = lruLinkedHashMap.get(key);
            if (null == value) {
                return -1;
            }
            lruLinkedHashMap.remove(key);
            lruLinkedHashMap.put(key, value);
            return value;
        }
    
        public void put(int key, int value) {
            if (lruLinkedHashMap.containsKey(key)) {
                lruLinkedHashMap.remove(key);
            }
            lruLinkedHashMap.put(key, value);
        }
    
    }
    
    

    参考链接:

     https://github.com/debitCrossBlockchain/interview__reference/blob/master/01.%E9%98%BF%E9%87%8C%E7%AF%87/1.1.4%20LRU%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6.md

    https://juejin.im/post/5c4eaa65e51d4552411b1b85

  • 相关阅读:
    细嚼慢咽C++primer(3)——引用形参,内联函数,重载函数,指向函数的指针
    面试突击(1)——数据结构基础,排序
    【Linux操作系统分析】进程的创建与可执行程序的加载
    List排序
    Dictionary 排序
    数据库连接串MSSQL、Oracle、Access
    String.IsNullOrEmpty()和String.IsNullOrWhiteSpace()
    jquery学习一 选择器
    sql查询问题
    int、string转enum;enum转int、string【C#】
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13312573.html
Copyright © 2011-2022 走看看