手撕时间复杂度为O(1)的LRU算法

LRU算法

LRU(Least Recently Used),即最近最少使用算法。常用于实现一个简单的缓存功能,就是把很久未使用的直接移除掉,只保留最近使用的。

LRU主要需要实现两个功能

添加缓存(涉及到删除缓存)
获取缓存

实现原理
一个单链表就能实现简单的LRU算法:但是链表的查找时间复杂度比较高了,是O(n)。

一个散列表+双链表实现一个O(1)复杂度的LRU算法:用散列表就可以直接定位某个缓存,时间复杂度O(1),但是散列表插入缓存之后,就没有了顺序,所以才需要一个链表来维护这个缓存的顺序,超过缓存最大容量之后需要删除未使用的缓存。而如果单链表删除某个缓存的话,又需要先遍历这个元素(时间复杂度O(n))才行。所以这里用双链表就可以在O(1)时间复杂度内删除这个缓存了。

package com.arithmetic.code;

import java.util.HashMap;
import java.util.Map;

public class LRUCache {

    private int cacheSize = 10; // map 长度
    private Map<String,Node> map = new HashMap<>();
    private Node head; // 头部节点
    private Node tail; // 尾部节点

    public void LRUCache(int cacheSize) {
        this.cacheSize = cacheSize;
    }

    class Node{
        String key;
        String value;
        Node pre; // 当前节点的上级引用
        Node next; // 当前节点的下级引用
        
        public Node(String key,String value){
            this.key = key;
            this.value = value;
        }
    }

    /**
     * 向缓存中添加值,head为最不常用值,tail为最新值
     * 链表头部为最不常用值,最新访问或者添加的元素置于链表尾部
     */
    public void  addCache(String key,String value){
        if(map.containsKey(key)){
            // 判断Node所在位置
            Node node = map.get(key);
            if(node.next != null){ // 说明node肯定不是在链表结尾
                if(node.pre == null){ // node位于头部
                    head = node.next;
                    // 注意:新的head节点需要将pre置为null,不可省略,否则新的头部节点会存在之前的上级引用
                    head.pre = null;
                } else { // node位于链表中间
                    node.pre.next = node.next;
                    node.next.pre = node.pre;
                }
                //此时将node 放在结尾
                tail.next = node;
                node.pre = tail;
                node.next = null;
                tail = node;
            }
        } else { // map中不存在该元素
            Node node = new Node(key,value);
            if(map.size() == cacheSize){ // map已到最大存储范围
                // 将头部节点删除,然后再尾部添加最新元素
                Node temp = head;
                head = head.next;
                // 新的head节点需要将pre置为null
                head.pre = null;
                map.remove(temp.key);
                //此时将node 放在尾部
                tail.next = node;
                node.pre = tail;
                node.next = null;
                tail = node;
            } else {
                if(map.size() == 0){
                    head = node;
                    tail = node;
                } else {
                    tail.next = node;
                    node.pre = tail;
                    // 新的head节点需要将pre置为null
                    node.next = null;
                    tail = node;
                }
            }
            map.put(key,node);
        }
    }

	// 从当前缓存中获取值
    public String getCache(String key){
        if(map.containsKey(key)){ // 现有元素包含key
            Node node = map.get(key);
            if(node.next != null){
                if(node.pre == null){ //  位于头部
                    head = node.next;
                    // 新的head节点需要将pre置为null
                    head.pre = null;
                } else {
                    node.pre.next = node.next;
                    node.next.pre = node.pre;
                }
                node.pre = tail;
                tail.next = node;
                node.next = null;
                tail = node;
            }
            return node.value;
        } else {
            return null;
        }
    }

	// 进行测试
    public static void main(String[] args) {
        LRUCache cache = new LRUCache();
        cache.addCache("key0", "value0");
        cache.addCache("key1", "value1");
        cache.addCache("key2", "value2");
        cache.addCache("key3", "value3");
        cache.addCache("key4", "value4");
        cache.addCache("key5", "value5");
        cache.addCache("key6", "value6");
        cache.addCache("key7", "value7");
        cache.addCache("key8", "value8");
        cache.addCache("key9", "value9");
        //从此处开始map已满,新添加的元素会将头部删除,自身放于链表末尾
        cache.addCache("key10", "value10");
        cache.addCache("key11", "value11");
        cache.addCache("key12", "value12");
        // debug 程序可知添加此node后 head 为key4,tail 为key13
        cache.addCache("key13", "value13");
        // debug 程序可知map中head 为key5,tail 为key4(即将自身从头部移动到尾部)
        cache.getCache("key4");
    }
}

你可能感兴趣的:(算法)