美文网首页
经典内存算法——LRU、LFU

经典内存算法——LRU、LFU

作者: _冻茶 | 来源:发表于2022-03-23 11:20 被阅读0次

    姓名:谭旭东

    学号:19011210016

    嵌牛导读:经典算法——LRU、LFU

    嵌牛鼻子:Java

    嵌牛提问:内存排除算法

    转载链接:http://182.92.190.128/archives/lrulfu(我的博客)

    # 一、LRU算法

    ## 1. 题目描述

    - Least Recently Used——最近最少使用

    - 淘汰机制:按照上一次使用时间进行淘汰

    - 题目出处:[面试题 16.25. LRU 缓存](https://leetcode-cn.com/problems/lru-cache-lcci/)

    - 关键实现:

    - capacity:容量,大于容量选择最久未使用资源淘汰

    - put():增加新资源

    - get():获取/使用资源,会更新资源使用状况

    ## 2. 思路

    - 使用双端队列,久未使用的资源放在队首,新加入/新使用的资源放在队尾

    - 淘汰时删除队首元素即可

    - 数据结构

    - HashMap:用于查询

    - LinkedList:用于构建链表

    ## 3. 代码

    ### (1)使用JAVA中LinkedHashMap

    - 细节:

    - 删除元素时,使用迭代器获取队首元素

    ```java

    // 1. 使用JAVA自带API:LinkedHashMap

    class LRUCache {

        int capacity;

        Map<Integer, Integer> map;

        public LRUCache(int capacity) {

            this.capacity = capacity;

            map = new LinkedHashMap<>();

        }

        public int get(int key) {

            if (!map.containsKey(key)) {

                return -1;

            }

            // 先删除旧的位置,再放入新位置

            Integer value = map.remove(key);

            map.put(key, value);

            return value;

        }

        public void put(int key, int value) {

            if (map.containsKey(key)) {

                map.remove(key);

                map.put(key, value);

                return;

            }

            map.put(key, value);

            // 超出capacity,删除最久没用的,利用迭代器删除第一个

            if (map.size() > capacity) {

                map.remove(map.entrySet().iterator().next().getKey());

            }

        }

    }

    ```

    ### (2)自建数据结构:HashMap + LinkedList

    - 代码细节:

    - ① moveToTail功能单独封装,put和get中都需要调用

    - ② 在put中调用get实现复用

    ```java

    // 2. 自己构造 LinkedHashMap + LinkedList(双链表)

    class LRUCache2 {

        private int capacity;

        private Map<Integer, ListNode> map;

        private ListNode head;

        private ListNode tail;

        public LRUCache2(int capacity) {

            this.capacity = capacity;

            this.map = new HashMap<>();

            this.head = new ListNode(-1, -1);

            this.tail = new ListNode(-1, -1);

            head.next = tail;

            tail.pre = head;

        }

        public int get(int key) {

            if (!map.containsKey(key))

                return -1;

            ListNode node = map.get(key);

            deleteNode(node);  // 链表中删除节点

            moveToTail(node);  // 节点移动到链表尾部

            return node.val;

        }

        public void put(int key, int value) {

            if (get(key) != -1) {      // 已经存在,改变值:在get内部会移动到尾部

                map.get(key).val = value;

                return;

            }

            ListNode node = new ListNode(key, value);

            map.put(key, node);

            moveToTail(node);      // 放到尾部去

            if (map.size() > capacity) {

                map.remove(head.next.key);

                deleteNode(head.next);

            }

        }

        private void moveToTail(ListNode node) {

            node.next = tail;

            node.pre = tail.pre;

            tail.pre.next = node;

            tail.pre = node;

        }

        private void deleteNode(ListNode node) {

            node.pre.next = node.next;

            node.next.pre = node.pre;

        }

        class ListNode {

            int key;

            int val;

            ListNode pre;

            ListNode next;

            public ListNode(int key, int val) {

                this.key = key;

                this.val = val;

                this.pre = null;

                this.next = null;

            }

        }

    }

    ```

    # 二、LFU算法

    ## 1. 题目描述

    - Least Frequently Used——最近使用频次最少

    - 淘汰机制:

    - ① 按照使用次数

    - ② 如果使用次数相同,按照使用时间淘汰

    - 题目出处:[LeetCode.460. LFU 缓存](https://leetcode-cn.com/problems/lfu-cache/)

    - 关键实现:

    - put()

    - get()

    ## 2. 思路

    - 数据结构:

    - ① 第一张HashMap,为了查询资源

    ![image.png](http://182.92.190.128/upload/2021/08/image-e4b03c91719e4d8688c06b19bc2cfb06.png)

    - ② 第二张HashMap,key值按照频次从小到大,value值为双向链表,保存了同频次的节点

    ![image.png](http://182.92.190.128/upload/2021/08/image-1edc13775f8d411596fac41252681de3.png)

    ## 3. 代码

    ```java

    import java.util.HashMap;

    import java.util.Map;

    /**

    *    自定义的LFU缓存类

    */

    public class LFUCache {

        /**

        *    双链表中的链表节点对象

        */

        protected static class Node{

            //对应输入的key

            private final int key;

            //对应输入的value

            private int value;

            //被访问的频率

            private int freq;

            //指向前一个节点的指针

            protected Node pre;

            //指向后一个节点的指针

            protected Node next;

            public Node(int key, int value, int freq) {

                this.key = key;

                this.value = value;

                this.freq = freq;

            }

            public Node(int key, int value, int freq, Node pre, Node next) {

                this.key = key;

                this.value = value;

                this.freq = freq;

                this.pre = null;

                this.next = null;

            }

            public void updateValue(int value) {

                this.value = value;

            }

            public void incrFreq() {

                ++this.freq;

            }

            public int getKey() {

                return this.key;

            }

            public int getValue() {

                return this.value;

            }

            public int getFreq() {

                return this.freq;

            }

            public static final Node createEmptyNode() {

                return new Node(-1,-1,-1,null,null);

            }

        }

        /**

        *  自定义的双向链表类

        */

        protected static class LinkedList {

            //双向链表的头结点

            private final Node head;

            //双向链表的尾节点

            private final Node tail;

            public LinkedList() {

                this.head = Node.createEmptyNode();

                this.tail = Node.createEmptyNode();

                this.head.next = this.tail;

                this.tail.pre = this.head;

            }

            /**

            * 将指定的节点插入到链表的第一个位置

            * @param node 将要插入的节点

            */

            public void insertFirst(Node node) {

                if(node==null) {

                    throw new IllegalArgumentException();

                }

                node.next = this.head.next;

                this.head.next.pre = node;

                node.pre = this.head;

                this.head.next = node;

            }

            /**

            * 从链表中删除指定的节点

            * @param node 将要删除的节点

            */

            public void deleteNode(Node node) {

                if(node==null) {

                    throw new IllegalArgumentException();

                }

                node.pre.next = node.next;

                node.next.pre = node.pre;

                node.pre = null;

                node.next = null;

            }

            /**

            * 从链表中获取最后一个节点

            * @return 双向链表中的最后一个节点,如果是空链表则返回None

            */

            public Node getLastNode() {

                if(this.head.next==this.tail) {

                    return Node.createEmptyNode();

                }

                return this.tail.pre;

            }

            /**

            * 判断链表是否为空,除了head和tail没有其他节点即为空链表

            * @return 链表不空返回True,否则返回False

            */

            public boolean isEmpty() {

                return this.head.next==this.tail;

            }

        }

        //key->Node 这种结构的哈希表

        private final Map<Integer,Node> keyMap = new HashMap<Integer,Node>();

        //freq->LinkedList 这种结构的哈希表

        private final Map<Integer,LinkedList> freqMap = new HashMap<Integer,LinkedList>();

        //缓存的最大容量

        private final int capacity;

        //记录缓存中最低频率

        private int minFreq = 0;

        public LFUCache(int capacity) {

    //  if(capacity<=0) {

    //  throw new IllegalArgumentException();

    //  }

            this.capacity = capacity;

        }

        /**

        * 获取一个元素,如果key不存在则返回-1,否则返回对应的value,同时更新被访问元素的频率

        * @param key 要查找的关键字

        * @return 如果没找到则返回-1,否则返回对应的value

        */

        public int get(int key) {

            if(!this.keyMap.containsKey(key)) {

                return -1;

            }

            Node node = this.keyMap.get(key);

            this.increment(node);

            return node.getValue();

        }

        /**

        * 插入指定的key和value,如果key存在则更新value,同时更新频率,

        * 如果key不存并且缓存满了,则删除频率最低的元素,并插入新元素。否则,直接插入新元素

        * @param key 要插入的关键字

        * @param value 要插入的值

        */

        public void put(int key, int value) {

            if(this.keyMap.containsKey(key)) {

                Node node = this.keyMap.get(key);

                node.updateValue(value);

                this.increment(node);

            }

            else {

                if(this.capacity==0) {

                    return;

                }

                if(this.keyMap.size()==this.capacity) {

                    this.remoteMinFreqNode();

                }

                Node node = new Node(key,value,1);

                this.increment(node,true);

                this.keyMap.put(key, node);

            }

        }

        /**

        *  更新节点的访问频率

        * @param node 要更新的节点

        */

        private void increment(Node node) {

            increment(node,false);

        }

        /**

        * 更新节点的访问频率

        * @param node 要更新的节点

        * @param isNewNode 是否是新节点,新插入的节点和非新插入节点更新逻辑不同

        */

        private void increment(Node node,boolean isNewNode) {

            if(isNewNode) {

                this.minFreq = 1;

                this.insertToLinkedList(node);

            }

            else {

                this.deleteNode(node);

                node.incrFreq();

                this.insertToLinkedList(node);

                if(!this.freqMap.containsKey(this.minFreq)) {

                    ++this.minFreq;

                }

            }

        }

        /**

        * 根据节点的频率,插入到对应的LinkedList中,如果LinkedList不存在则创建

        * @param node 将要插入到LinkedList的节点

        */

        private void insertToLinkedList(Node node) {

            if(!this.freqMap.containsKey(node.getFreq())) {

                this.freqMap.put(node.getFreq(), new LinkedList());

            }

            LinkedList linkedList = this.freqMap.get(node.getFreq());

            linkedList.insertFirst(node);

        }

        /**

        * 删除指定的节点,如果节点删除后,对应的双链表为空,则从__freqMap中删除这个链表

        * @param node 将要删除的节点

        */

        private void deleteNode(Node node) {

            LinkedList linkedList = this.freqMap.get(node.getFreq());

            linkedList.deleteNode(node);

            if(linkedList.isEmpty()) {

                this.freqMap.remove(node.getFreq());

            }

        }

        /**

        * 删除频率最低的元素,从freqMap和keyMap中都要删除这个节点,

        * 如果节点删除后对应的链表为空,则要从__freqMap中删除这个链表

        */

        private void remoteMinFreqNode() {

            LinkedList linkedList = this.freqMap.get(this.minFreq);

            Node node = linkedList.getLastNode();

            linkedList.deleteNode(node);

            this.keyMap.remove(node.getKey());

            if(linkedList.isEmpty()) {

                this.freqMap.remove(node.getFreq());

            }

        }

    }

    ```

    相关文章

      网友评论

          本文标题:经典内存算法——LRU、LFU

          本文链接:https://www.haomeiwen.com/subject/fpscjrtx.html