zoukankan      html  css  js  c++  java
  • LFU缓存结构

    LFU

    ​ LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。实现一个LFU缓存结构需要实现如下功能:

    • set(key, value):将记录(key, value)插入该结构
    • get(key):返回key对应的value值

    ​ 但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;

    ​ 如果调用次数最少的key有多个,上次调用发生最早的key被删除

    ​ 这就是LFU缓存替换算法。实现这个结构,K作为参数给出

    [要求]

    set和get方法的时间复杂度为O(1)

    解决思路

    ​ 整体结构设计成发生set和get操作次数一样的key,都放在一个双向链表中(桶)。对于不同的操作次数,分别设置桶,然后桶和桶之间按照操作次数从小到大串起来,桶和桶之间也看作是一个双向链表。如下图所示:

    ​ 该结构中,数据进行更新的规则是:

    ​ 当某一个key发生set和get时,来到key所在的位置,把key从原来的桶中去掉,也就是key的上一个节点和下一个节点直接相连,然后把key放在操作次数+1的桶中,

    ​ 如果没有次数+1的桶就新建,保证桶之间依然是双向链表的的链接;

    ​ 如果已经有次数加1的桶,就把key放在这个桶的头部

    ​ 如果key原来所在的桶中只有key这一条记录,就删除原来的桶,保证桶之间仍然是双向链表的链接。

    ​ 需要注意的是,桶和桶之间是双向链表,桶内部的的节点之间也是双向链表。本结构的难点在于,实现代码时需要维系桶之间、节点之间始终是双向链表这个关系。当结构中记录已经达到了k条,此时又有新的节点进来时,就需要删除一个节点,删除最左边的桶的尾节点即可。

    ​ 实现时需要借助两个map,第一个map的key和value分别是节点的key和节点对象node;第二个map的key和value分别是节点对象node和节点对象所在的桶NodeList。

    实现代码

    import java.util.HashMap;
    
    public class LFUCache {
    
        private int capacity; // 缓存容量
        private int size; // 缓存中当前的节点数量
        private HashMap<Integer,Node> records;
        // 表示节点Node在哪个桶中
        private HashMap<Node,NodeList> heads;
        private NodeList headList; //整个结构中位于最左边的桶
    
        public LFU(int K){
    
            this.capacity = K;
            this.size = 0;
            this.records = new HashMap<>();
            this.heads = new HashMap<>();
            headList = null;
        }
    
        /**
         * 该函数的功能是:判断刚刚减少了一个节点的桶是否已经为空
         * 1、如果不为空,则什么也不做
         *
         * 2、如果为空,且removeNodeList是整个缓存结构中最左边的桶,则删除这个桶,同时让缓存结构
         * 中最左边的桶变为removeNodeList的下一个桶
         *
         * 3、如果为空,且removeNodeList不是整个缓存结构中最左边的桶,则删除这个桶,将removeNodeList前后的桶修改为
         * 直接双向连接
         * @param removeNodeList 表示刚刚减少了一个Node节点的桶
         * @return 返回值表示removeNodeList是否已经为空,空则返回true,否则返回false
         */
        private boolean modifyHeadList(NodeList removeNodeList){
    
            if (removeNodeList.isEmpty()){
    
                if (headList == removeNodeList){
    
                    headList = removeNodeList.next;
                    // 避免此时只有一个removeNodeList一个桶,而造成空指针异常
                    if (headList != null){
                        headList.prev = null;
                    }
    
                }else {
    
                    removeNodeList.prev.next = removeNodeList.next;
    
                    // 避免removeNodeList是最右边的桶,然后造成removeNodeList.next.prev产生空指针异常
                    if (removeNodeList.next != null){
                        removeNodeList.next.prev = removeNodeList.prev;
                    }
                }
    
                return true;
            }
    
            return false;
        }
    
        /**
         * 函数功能:当node节点的操作次数加1了,将这个节点从原来的桶中放入操作次数+1的桶中
         * 整个过程要保证桶之间仍然是双向链表,也要保证节点之间仍然是双向链表
         * @param node 要移动位置的节点
         * @param oldNodeList node未移动位置之前所在的桶
         */
        private void move(Node node,NodeList oldNodeList){
    
            oldNodeList.deleteNode(node);
    
            // preList表示操作次数加1的后对应的那个桶的前一个桶
            // 如果oldNodeList删除node之后,空了,此时需要将oldNodeList删掉,那么次数加1后对应的那个桶的前一个桶就是oldNodeList.prev
            // 如果oldNodeList删除node之后,不空,那么oldNodeList依然是次数加1后对应的那个桶的前一个桶
            NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.prev : oldNodeList;
    		
            // nextList表示操作次数加1后对应的那个桶或者操作次数加1后的那个桶的下一个桶
            NodeList nextList = oldNodeList.next;
    
            if (nextList == null){
    
                NodeList newList = new NodeList(node);
                if (preList != null){
                    preList.next = newList;
                }
                newList.prev = preList;
    
                if (headList == null){
                    headList = newList;
                }
                heads.put(node,newList);
    
            }else {
    
                if (nextList.head.times == node.times){
    
                    nextList.addNodeToHead(node);
                    heads.put(node,nextList);
    
                }else {
    
                    NodeList newList = new NodeList(node);
                    if (preList != null){
                        preList.next = newList;
                    }
    
                    newList.prev = preList;
                    newList.next = nextList;
                    nextList.prev = newList;
    
                    if (headList == nextList){
                        headList = newList;
                    }
    
                    heads.put(node,newList);
                }
            }
    
        }
    
        public void set(int key,int value){
    
            if (records.containsKey(key)){
    
                Node node = records.get(key);
                node.value = value;
                node.times ++;
                NodeList curNodeList = heads.get(node);
                move(node,curNodeList);
            }else {
    
                if (size == capacity){
                    Node node = headList.tail;
                    headList.deleteNode(node);
                    modifyHeadList(headList);
                    records.remove(node.key);
                    heads.remove(node);
                    size --;
                }
    
                Node node = new Node(key, value, 1);
                if (headList == null){
    
                    headList = new NodeList(node);
                }else {
    
                    if (headList.head.times == node.times){
    
                        headList.addNodeToHead(node);
                    }else {
    
                        NodeList newList = new NodeList(node);
                        newList.next = headList;
                        headList.prev = newList;
                        headList = newList;
                    }
                }
                records.put(key,node);
                heads.put(node,headList);
                size ++;
            }
        }
    
        public int get(int key){
    
            if (!records.containsKey(key)){
                return -1;
            }
    
            Node node = records.get(key);
            node.times ++;
            NodeList curNodeList = heads.get(node);
            move(node,curNodeList);
            return node.value;
        }
    
        /**
         * 节点的数据结构
         */
        class Node{
    
            public int key;
            public int value;
            public int times; // 该节点发生set和get的次数总和
    
            public Node next;
            public Node prev;
    
            public Node(int key,int value,int times){
    
                this.key = key;
                this.value = value;
                this.times = times;
            }
        }
    
        /**
         * 桶的数据结构
         */
        class NodeList{
    
            public Node head; // 指向桶的头节点的指针
            public Node tail; // 指向桶的尾节点的指针
            public NodeList next;
            public NodeList prev;
    
            // 初始化
            public NodeList(Node node){
    
                head = node;
                tail = node;
            }
    
            /**
             * 将一个新的节点加入桶中,新的节点作为桶中新的头节点
             * @param newNode
             */
            public void addNodeToHead(Node newNode){
                newNode.next = head;
                head.prev = newNode;
                head = newNode;
            }
    
            /**
             * 删除Node节点并保证node的上下文环境重新连接
             * @param node
             */
            public void deleteNode(Node node){
    
                if (head == tail){
                    head = null;
                    tail = null;
                }else {
    
                    if (head == node){
    
                        head = node.next;
                        head.prev = null;
                    }else if (node == tail){
    
                        tail = node.prev;
                        tail.next = null;
    
                    }else {
    
                        node.next.prev = node.prev;
                        node.prev.next = node.next;
                    }
                }
                node.next = null;
                node.prev = null;
            }
    
            public boolean isEmpty(){
    
                return head == null;
            }
    
        }
    }
    
  • 相关阅读:
    组装query,query汇总,query字段
    POJ 1276, Cash Machine
    POJ 1129, Channel Allocation
    POJ 2531, Network Saboteur
    POJ 1837, Balance
    POJ 3278, Catch That Cow
    POJ 2676, Sudoku
    POJ 3126, Prime Path
    POJ 3414, Pots
    POJ 1426, Find The Multiple
  • 原文地址:https://www.cnblogs.com/yxym2016/p/14627285.html
Copyright © 2011-2022 走看看