zoukankan      html  css  js  c++  java
  • O(1)复杂度实现LFU

    参考自这篇文章:LFU五种实现方式,从简单到复杂,实现了其中第五种方法。

    数据结构如下:

    1. DoubleLinkedList

      每个节点代表不同的频次,按照从高到低的顺序。每个节点内部又维护了一个Node链表,用来存储同一频次下不同时间访问的缓存,按照时间顺序,从前到后,最前的是最近被访问的缓存。

    //存储不同的频率,频率从高到低
    public static class DoubleLinkedList {
        //前向节点
        public DoubleLinkedList pre;
        //后向节点
        public DoubleLinkedList next;
        //当前节点的访问评率
        public int freq;
        //内部节点头节点,方便新增时O(1)复杂度插入
        public Node head;
        //内部节点尾节点,方便删除旧节点时O(1)复杂度删除
        public Node tail;
        //记录Node的大小
        public int size;
    
        DoubleLinkedList(int freq) {
            head = new Node(freq);
            tail = new Node(freq);
            head.next = tail;
            tail.pre = head;
            this.freq = freq;
            size = 0;
        }
    
        //内部元素,是一个双向链表,存储同一频率下不同的节点,先后顺序就是访问顺序
        public static class Node {
            public Node pre;
            public Node next;
            public String key;
            public String value;
            //找前一个外层节点
            public int freq;
            //记录一下,freqInc方法会用
            public DoubleLinkedList doubleLinkedList;
    
            Node(String key, String values) {
                this.key = key;
                this.value = values;
                freq = 1;
            }
    
            Node(int freq) {
                this.freq = freq;
            }
        }
    
        public void deleteNode(Node node) {
            node.pre.next = node.next;
            node.next.pre = node.pre;
            size--;
        }
    
        //删除尾节点之前的一个节点
        public Node deleteLastNode() {
            Node node = tail.pre;
            tail.pre.pre.next = tail;
            tail.pre = tail.pre.pre;
            size--;
            return node;
        }
    
        //新增节点放在最前
        public void addNode(Node node) {
            head.next.pre = node;
            node.next = head.next;
            head.next = node;
            node.pre = head;
            node.doubleLinkedList = this;
            size++;
        }
    
    }

    2. 实现代码

    public class LFUCache {
        //初始容量
        private int cap;
        //缓存数,可以用map.size()替换
        private int size;
        //头节点
        private DoubleLinkedList head;
        //尾节点
        private DoubleLinkedList tail;
        //O(1)拿到缓存
        private Map<String/**/, DoubleLinkedList.Node/*缓存*/> map;
    
    
        LFUCache(int cap) {
            this.cap = cap;
            head = new DoubleLinkedList(-1);
            tail = new DoubleLinkedList(-1);
            head.next = tail;
            tail.pre = head;
            map = new HashMap<>();
        }
    
        /**
         * 获取缓存
         *
         * @param key 键
         * @return*/
        public String get(String key) {
            if (!map.containsKey(key)) {
                return null;
            }
            DoubleLinkedList.Node node = map.get(key);
            //更新频次
            freqInc(node);
            return node.value;
        }
    
        /**
         * 新增缓存
         *
         * @param key   键
         * @param value 值
         */
        public void add(String key, String value) {
            if (!map.containsKey(key)) { //如果不存在
                //是否超过容量
                if (map.size() == cap) {
                    //将最小频率,最久未访问的节点删除
                    DoubleLinkedList pre = tail.pre;
                    DoubleLinkedList.Node node = pre.deleteLastNode();
                    //当外层节点的内层节点为空,删除这个节点
                    if (pre.size == 0) {
                        deleteDoubleLinkedList(pre);
                    }
                    map.remove(node.key);
                }
                //没有溢出的情况
                DoubleLinkedList.Node node = new DoubleLinkedList.Node(key, value);
                map.put(key, node);
                //找到最小的链表
                DoubleLinkedList pre = tail.pre;
                if (pre.freq != node.freq) {
                    //新增外层节点
                    DoubleLinkedList newLinkedList = new DoubleLinkedList(1);
                    addDoubleLinkedList(pre, newLinkedList);
                    newLinkedList.addNode(node);
                } else {
                    pre.addNode(node);
                }
            } else {
                //已经存在,这时候更新值,并且修改节点的频次,跟get类似
                DoubleLinkedList.Node node = map.get(key);
                node.value = value;
                //移动到新的频次
                freqInc(node);
            }
        }
    
    
        //更新频次
        public void freqInc(DoubleLinkedList.Node node) {
            //将节点移动到新的频率下
            //1. 从原外层链表中移除
            DoubleLinkedList oldLinkedList = node.doubleLinkedList;
            oldLinkedList.deleteNode(node);
            //1.1 删除完成后,如果没有元素,删除外层链表
            if (oldLinkedList.size == 0) {
                deleteDoubleLinkedList(oldLinkedList);
            }
            //2. 频率加一
            node.freq = node.freq + 1;
            //3. 移动到新外层链表
            DoubleLinkedList pre = oldLinkedList.pre;
            if (pre.freq != node.freq) {
                //3.1 前一个节点频次不匹配,则新建一个外层节点
                DoubleLinkedList newLinkedList = new DoubleLinkedList(node.freq);
                //3.2 插入到外层链表
                addDoubleLinkedList(pre, newLinkedList);
                newLinkedList.addNode(node);
            } else {
                pre.addNode(node);
            }
    
        }
    
    
        private void deleteDoubleLinkedList(DoubleLinkedList doubleLinkedList) {
            doubleLinkedList.pre.next = doubleLinkedList.next;
            doubleLinkedList.next.pre = doubleLinkedList.pre;
        }
    
        //在某个节点之后新增一个节点
        private void addDoubleLinkedList(DoubleLinkedList preLinkedList, DoubleLinkedList newLinkedList) {
            preLinkedList.next.pre = newLinkedList;
            newLinkedList.next = preLinkedList.next;
            preLinkedList.next = newLinkedList;
            newLinkedList.pre = preLinkedList;
        }
    
    
        //存储不同的频率,频率从高到低
        public static class DoubleLinkedList {
            //...
        }
    
    
        public static void main(String[] args) {
            LFUCache cache = new LFUCache(2);
            cache.add("1", "苹果");
            cache.add("2", "香蕉");
            System.out.println(cache.get("1"));
            cache.add("3", "荔枝");//删除2,因为1访问频次更高
            System.out.println(cache.get("2"));
            System.out.println(cache.get("3"));
            cache.add("4", "桃子");//删除1,因为1比3更久未被访问
            System.out.println(cache.get("1"));
            System.out.println(cache.get("3"));
            System.out.println(cache.get("4"));
        }
    
    }
    人生就像蒲公英,看似自由,其实身不由己。
  • 相关阅读:
    Java 位运算
    Java 自增运算
    Java 变量命名规范
    Java 数据类型
    Java 环境配置
    SQL Server-语句类别、数据库范式、系统数据库组成(一)
    Socket连接时,端口是怎么分配的
    【转载】SQL执行计划
    前端页面播放 rtmp 流与 flv 格式视频文件
    C# Winform Soket 网络编程 多个客户端连接服务器并返回客户端操作请求
  • 原文地址:https://www.cnblogs.com/walker993/p/14854312.html
Copyright © 2011-2022 走看看