算法笔记——LRU和LFU缓存结构

LRU缓存结构

问题描述:

设计可以变更的缓存结构(LRU)
【题目】
设计一种缓存结构,该结构在构造时确定大小,假设大小为K,并有两个功能:
set(key,value):将记录(key,value)插入该结构。
get(key):返回key对应的value值。
【要求】
1.set和get方法的时间复杂度为O(1)。
2.某个key的set或get操作一旦发生,认为这个key的记录成了最经常使用的。
3.当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
【举例】
假设缓存结构的实例是cache,大小为3,并依次发生如下行为:
1.cache.set("A",1)。最经常使用的记录为("A",1)。
2.cache.set("B",2)。最经常使用的记录为("B",2),("A",1)变为最不经常的。
3.cache.set("C",3)。最经常使用的记录为("C",2),("A",1)还是最不经常的。
4.cache.get("A")。最经常使用的记录为("A",1),("B",2)变为最不经常的。
5.cache.set("D",4)。大小超过了3,所以移除此时最不经常使用的记录("B",2),
加入记录 ("D",4),并且为最经常使用的记录,然后("C",2)变为最不经常使用的
记录

分析问题
  • 简单分析LRU结构需要以下几个功能
  • 必须有明确的大小K,可在构造方法中传入
  • set方法为键值对的形式,所以必须准备HasnMap,才能使得set和get方法的时间复杂度为O(1);
  • 调整某个key成为最经常用的节点,所以可以维护一个关于value的双向链表,那么这里直接做一个Node节点,value作为他的属性之一。
  • 当大小超载时,移除最不经常使用的,假设双端链表尾部就是最经常使用的节点,双端链表头部最不进场使用,所以这里直接把头节点删掉就可以了
  • 一旦对一个节点进行修改了,那么需要将修改的节点移动到双端聊表尾部。
代码
    static class Node{
        V value;
        Node last;
        Node next;

        public Node(V value) {
            this.value = value;
        }
    }

    static class DoubleLinkedList{
        Node head;
        Node tail;

        public DoubleLinkedList(){
            this.head = null;
            this.tail = null;
        }

        // 添加一个节点
        void addNode(Node node){
            if(head == null){
                this.head = node;
                this.tail = node;
            }else{
                this.tail.next = node;
                node.last = this.tail;
                this.tail = node;
            }
        }

        // 将某个节点node移动到链表尾部
        void moveNodeToTail(Node node){
            if(this.tail == node){
                return;
            }
            if(this.head == node){
                this.head = node.next;
                this.head.last = null;
            }else{
                node.last.next = node.next;
                node.next.last = node.last;
            }
            node.last = this.tail;
            node.next = null;
            this.tail.next = node;
            this.tail = node;
        }

        // 移除双向链表的头节点
        Node removeHeadNode(){
            if(this.head == null) {
                return null;
            }
            Node res = this.head;
            if(this.head == this.tail){
                this.head = null;
                this.tail = null;
            }else{
                this.head = res.next;
                res.next = null;
                this.head.last = null;
            }
            return res;
        }
    }

    public static class MyCache{
        HashMap> keyNodeMap;
        HashMap,K> valueKeyMap;
        DoubleLinkedList nodeList;
        int capacity;

        public MyCache(int capacity) {
            if(capacity < 1){
                throw new RuntimeException("容量不可以小于1");
            }
            this.capacity = capacity;
            this.keyNodeMap = new HashMap<>();
            this.valueKeyMap = new HashMap<>();
            this.nodeList = new DoubleLinkedList<>();
        }

        // 根据指定的K获取value
        public V get(K key){
            if(this.keyNodeMap.containsKey(key)){
                Node res = this.keyNodeMap.get(key);
                this.nodeList.moveNodeToTail(res);
                return res.value;
            }
            return null;
        }
        
        // 向缓存结构中添加键值对
        public void set(K key,V value){
            if(this.keyNodeMap.containsKey(key)){
                Node cur = this.keyNodeMap.get(key);
                cur.value = value;
                this.nodeList.moveNodeToTail(cur);
            }else{
                Node temp = new Node(value);
                this.keyNodeMap.put(key,temp);
                this.valueKeyMap.put(temp,key);
                this.nodeList.addNode(temp);
                if(this.keyNodeMap.size() == this.capacity + 1){
                    Node removeValue = this.nodeList.removeHeadNode();
                    K removeKey = this.valueKeyMap.get(removeValue);
                    this.keyNodeMap.remove(removeKey);
                    this.valueKeyMap.remove(removeValue);
                }
            }
        }
    }

LFU缓存结构

LeetCode | 460. LFU缓存

题目描述

请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get 和 put。
- get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
- put(key, value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近 最少使用的键。
【项的使用次数】就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。

基本思路
  • 同LRU结构,将键值对存储在Node节点中。
  • 创建一个双向链表,链表中每一个位置都是一个双向链表,所有具有相同操作次数的节点node放在同一个位置重新构成双向链表
  • 所以当插入一个元素时,如果该元素存在,则需要将其操作次数加1,然后将其添加到大链表的新的位置中;如果不存在,则创建该节点,默认操作次数为1
  • 获取一个元素时,该Node的操作次数也加1,然后调整到大链表新的位置
  • 剩下的问题就是扣边界了。
代码
// 首先定义节点类型
class Node{
    int key;
    int value;
    int times;
    Node up;
    Node down;

    public Node(int key, int value, int times){
        this.key = key;
        this.value = value;
        this.times = times;
    }
}

//定义NodeList类,LFU包含一个由NodeList构成的双端链表
class NodeList{
    Node head;
    Node tail;
    NodeList last;
    NodeList next;

    public NodeList(Node node){
        this.head = node;
        this.tail = node;
    }

    // 将一个节点node插入此nodeList的头步
    public void addNodeFromHead(Node newNode){
        newNode.down = this.head;
        this.head.up = newNode;
        this.head = newNode;
    }

    // 判断当前的nodeList是否为null
    public boolean isEmpty(){
        return this.head == null;
    }

    // 从当前的nodeList中删除一个node
    public void deleteNode(Node node){
        if(this.head == this.tail){
            this.head = null;
            this.tail = null;
        }else{
            if(this.head == node){
                this.head = node.down;
                this.head.up = null;
            }else if(this.tail == node){
                this.tail = node.up;
                this.tail.down = null;
            }else{
                node.up.down = node.down;
                node.down.up = node.up;
            }
        }
        node.up = null;
        node.down = null;
    }
}

class LFUCache {
    // 定义成员变量
    int capacity;
    int size;
    HashMap records;
    HashMap heads;
    NodeList headList;

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        this.records = new HashMap();
        this.heads = new HashMap();
        this.headList = null;
    }
    
    public int get(int key) {
        int res  = -1;
        if(records.containsKey(key)){
            Node node = records.get(key);
            res = node.value;
            node.times++;
            NodeList curNodeList = heads.get(node);
            move(node,curNodeList);
        }
        return res;
    }
    
    public void put(int key, int value) {
        if(this.capacity == 0) {
            return;
        }
        // 如果当前的key指向的节点存在
        if(records.containsKey(key)){
            Node node = records.get(key);
            node.value = value;
            node.times++;
            NodeList curNodeList = heads.get(node);  // 获取当前的node存在的NodeList
            move(node,curNodeList);    // 将node移动到新的NodeList中,并调正curNodeList
        }
        // 如果当前的key指向的节点不存在
        else{
            //r如果容量超限
            if(this.size == this.capacity){
                //删除headList的tail
                Node tail = this.headList.tail;
                this.headList.deleteNode(tail);
                modifyHeadList(this.headList); // 更新当前的headList,如果为null调整
                records.remove(tail.key);
                heads.remove(tail);
                this.size--;
            }
            // 创建新的Node进行插入
            Node node = new Node(key,value,1);
            if(this.headList == null){
                this.headList = new NodeList(node);
            }else{
                if(this.headList.head.times == node.times){
                    this.headList.addNodeFromHead(node);
                }else{
                    NodeList newList = new NodeList(node);
                    newList.next = this.headList;
                    this.headList.last = newList;
                    this.headList = newList;
                }
            }
            records.put(key,node);
            heads.put(node,this.headList);
            this.size++;
        }
    }

    private void move(Node node, NodeList oldNodeList){
        // 首先从原来的NodeList中删除node节点
        oldNodeList.deleteNode(node);
        NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.last : oldNodeList;  // 调整oldNodeList
        NodeList nextList = oldNodeList.next;
        if(nextList == null){
            // 新建一个Nodelist,把node扔进去
            NodeList newList = new NodeList(node);
            if(preList != null){
                preList.next = newList;
            }
            newList.last = preList;
            if(this.headList == null){
                this.headList = newList;
            }
            heads.put(node,newList);
        }else{
            if(nextList.head.times == node.times){
                // 将node放到头步
                nextList.addNodeFromHead(node);
                heads.put(node,nextList);
            }else{
                NodeList newList =  new NodeList(node);
                if(preList != null){
                    preList.next = newList;
                }
                newList.last = preList;
                newList.next = nextList;
                nextList.last = newList;
                if(this.headList == nextList){
                     this.headList = newList;
                }
                heads.put(node,newList);
            }
        }
    }

    private boolean modifyHeadList(NodeList nodeList){
        // 如果当前的nodeList为null,则要进行删除
        if(nodeList.isEmpty()){
            if(this.headList == nodeList){
                this.headList = nodeList.next;
                if(this.headList != null){
                    this.headList.last = null;
                }
            }else{
                nodeList.last.next = nodeList.next;
                if(nodeList.next != null){
                    nodeList.next.last = nodeList.last;
                }
            }
            return true;
        }
        //否则返回false
        return false;
    }
}

你可能感兴趣的:(算法笔记——LRU和LFU缓存结构)