zoukankan      html  css  js  c++  java
  • 写一个 LRU 缓存函数(#146)

    什么是LRU缓存函数?

    LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。

    回到正题,

    运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制 。
    实现 LRUCache 类:

    LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
    int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
    void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

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

    示例:

    输入
    ["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
    [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
    输出
    [null, null, null, 1, null, -1, null, -1, 3, 4]

    解释
    LRUCache lRUCache = new LRUCache(2);
    lRUCache.put(1, 1); // 缓存是 {1=1}
    lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
    lRUCache.get(1); // 返回 1
    lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
    lRUCache.get(2); // 返回 -1 (未找到)
    lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
    lRUCache.get(1); // 返回 -1 (未找到)
    lRUCache.get(3); // 返回 3
    lRUCache.get(4); // 返回 4

    思路:

    这个缓存器主要有两个成员函数,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,如果存在就删掉原有项,并在顶部插入新来项,然后判断是否溢出,若溢出则删掉底部项(最不常用项)。

    解一:

    /**
     * @param {number} capacity
     */
    var LRUCache = function(capacity) {
        this.capacity = capacity;
        this.listHead = null;
        this.listTail = null;
        this.map = {};
        this.len = 0;
    };
    
    /** 
     * @param {number} key
     * @return {number}
     */
    LRUCache.prototype.get = function(key) {
        let node = this.map[key];
        if (typeof node === 'object') {
            if (this.listTail !== node) {
                if (node.prev) {
                    node.prev.next = node.next;
                    node.next.prev = node.prev;
                } else {
                    this.listHead = node.next;
                    this.listHead.prev = null;
                }
                this.listTail.next = node;
                node.prev = this.listTail;
                node.next = null;
                this.listTail = node;
            }
            
            return node.val;
        } else {
            return -1;
        }
    };
    
    /** 
     * @param {number} key 
     * @param {number} value
     * @return {void}
     */
    LRUCache.prototype.put = function(key, value) {
        if (this.get(key) !== -1) {
            this.map[key].val = value;
            return;
        }
    
        let newNode = new Node(key, value);
        this.map[key] = newNode;
        if (this.len === this.capacity) {
            if (!this.listHead) return;
            delete this.map[this.listHead.key];
            this.listHead = this.listHead.next;
            if (this.listHead) this.listHead.prev = null;
            this.listTail.next = newNode;
            newNode.prev = this.listTail;
            this.listTail = newNode;
        } else {
            if (!this.listHead) {
                this.listHead = this.listTail = newNode;
            } else {
                this.listTail.next = newNode;
                newNode.prev = this.listTail;
                this.listTail = newNode;
            }
            this.len++;
        }
    };
    
    function Node(key, val) {
        this.key = key;
        this.val = val;
        this.next = null;
        this.prev = null;
    }

    解二:

    //  一个Map对象在迭代时会根据对象中元素的插入顺序来进行
    // 新添加的元素会被插入到map的末尾,整个栈倒序查看
    class LRUCache {
      constructor(capacity) {
        this.secretKey = new Map();
        this.capacity = capacity;
      }
      get(key) {
        if (this.secretKey.has(key)) {
          let tempValue = this.secretKey.get(key);
          this.secretKey.delete(key);
          this.secretKey.set(key, tempValue);
          return tempValue;
        }
        else return -1;
      }
      put(key, value) {
        // key存在,仅修改值
        if (this.secretKey.has(key)) {
          this.secretKey.delete(key);
          this.secretKey.set(key, value);
        }
        // key不存在,cache未满
        else if(this.secretKey.size<this.capacity){
          this.secretKey.set(key, value);
        }
        // 添加新key,删除旧key
        else{
          this.secretKey.set(key,value);
          // 删除map的第一个元素,即为最长未使用的
          this.secretKey.delete(this.secretKey.keys().next().value);
        }
      }
    }
    let cache = new LRUCache(2);
    cache.put(1, 1);
    cache.put(2, 2);
    console.log("cache.get(1)", cache.get(1))// 返回  1
    cache.put(3, 3);// 该操作会使得密钥 2 作废
    console.log("cache.get(2)", cache.get(2))// 返回 -1 (未找到)
    cache.put(4, 4);// 该操作会使得密钥 1 作废
    console.log("cache.get(1)", cache.get(1))// 返回 -1 (未找到)
    console.log("cache.get(3)", cache.get(3))// 返回  3
    console.log("cache.get(4)", cache.get(4))// 返回  4
  • 相关阅读:
    poj 1088 滑雪
    位运算与bitset
    hdu 4607 Park Visit
    树的直径
    codeforces 495D Sonya and Matrix
    German Collegiate Programming Contest 2015(第三场)
    BAPC 2014 Preliminary(第一场)
    Benelux Algorithm Programming Contest 2014 Final(第二场)
    E. Reachability from the Capital(tarjan+dfs)
    poj2104 K-th Number(划分树)
  • 原文地址:https://www.cnblogs.com/xulei1992/p/15111491.html
Copyright © 2011-2022 走看看