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"));
    }

}

 

posted @ 2021-06-06 00:12  walker993  阅读(179)  评论(0编辑  收藏  举报