LRUCache的Java实现

文章目录

  • 思路介绍
  • LinkedHashMap实现
  • ArrayList实现
  • 反例:对哈希链表的误解
  • 手动哈希链表实现
  • LeetCode传送门

思路介绍

LRU缓存的机制非常容易理解,代码实现思路也非常简单

  • get方法时,如果元素存在,记得更新其位置信息
  • put方法时,要根据容量决定是否需要删除oldest元素

LinkedHashMap实现

首先给出最简单的写法,JDK内部实现的哈希链表

/**
 * @Classname LRUCache
 * @Description 直接用LinkedHashMap
 * @Date 2019/12/14 7:45
 * @Created by Jesse
 */
public class LRUCache {
    private LinkedHashMap<Integer,Integer> data;
    public LRUCache(int capacity) {
        // 注意这个loadFactor是一个float,所以需要加f
        data = new LinkedHashMap<Integer,Integer>(capacity, 0.75f, true){
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size()>capacity;
            }
        };
    }

    public int get(int key) {
        return data.containsKey(key)?data.get(key):-1;
    }

    public void put(int key, int value) {
        data.put(key, value);
    }
}

ArrayList实现

用数组或者链表实现思路差不多,而选用动态数组的话,可以省去很多操作(要不然元素移动之类的东西还得自己写)

/**
 * @Classname LRUCache2
 * @Description 动态数组实现
 * @Date 2019/12/14 7:52
 * @Created by Jesse
 */
public class LRUCache2 {

    class Node{
        Integer key;
        Integer value;

        public Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    
    ArrayList<Node> data;
    private int capacity;

    public LRUCache2(int capacity) {
        data = new ArrayList<>(capacity);
        this.capacity = capacity;
    }

    public int get(int key) {
        int nodeIndex = getNodeIndex(key);
        if (nodeIndex > -1) {
            Node node = data.get(nodeIndex);
            data.remove(nodeIndex);
            data.add(node);
            return node.value;
        }
        return -1;
    }

    public void put(int key, int value) {
        int nodeIndex = getNodeIndex(key);
        if (nodeIndex == -1) {
            if (data.size() >= capacity) {
                data.remove(0);
            }
            data.add(new Node(key, value));
        } else {
            data.remove(nodeIndex);
            data.add(new Node(key,value));
        }
    }

    private int getNodeIndex(int key) {
        for (int i = 0; i < data.size(); i++) {
            if (data.get(i).key.equals(key)) {
                return i;
            }
        }
        return -1;
    }
}

反例:对哈希链表的误解

年轻的我曾经以为,链表+哈希表就是哈希链表了~naive

这是个反例,虽然功能可以实现,但是效率很低,实属费力不讨好

/**
 * @Classname LRUCache3
 * @Description 哈希表+链表 != 哈希链表,复杂度并没有降低,很慢
 * @Date 2019/12/14 8:27
 * @Created by Jesse
 */
public class LRUCache3 {
    private HashMap<Integer, Integer> map;
    private LinkedList<Integer> linkedList;
    private int capacity;

    public LRUCache3(int capacity) {
        map = new HashMap<>(capacity);
        linkedList = new LinkedList<>();
        this.capacity = capacity;
    }

    public int get(int key) {
        if (map.containsKey(key)) {
            linkedList.remove(new Integer(key));
            linkedList.add(key);
            return map.get(key);
        } else {
            return -1;
        }

    }

    public void put(int key, int value) {
        if (map.containsKey(key)) {
            linkedList.remove(new Integer(key));
            linkedList.add(key);
            map.put(key, value);
        } else {
            if (map.size() >= capacity) {
                Integer remove = linkedList.remove(0);
                map.remove(remove);
            }
            map.put(key, value);
            linkedList.add(key);
        }
    }
}

手动哈希链表实现

这是正经实现的哈希链表,参考小灰的《漫画算法》

我们来看一哈希链表有哪些优势:

  • 增加元素时,只需要向哈希表中添加元素,再把链表指针移动一下,时间复杂度为 O ( 1 ) {O(1)} O(1)
  • 删除元素时,因为我们用的是双链表,所以时间复杂度也是 O ( 1 ) {O(1)} O(1)(双链表VS单链表,也是一种空间换时间的思想)
  • 修改元素显然也是 O ( 1 ) {O(1)} O(1)
  • 查找也是 O ( 1 ) {O(1)} O(1)
  • 还弥补了哈希表无法记录插入顺序的缺点,可以说是非常棒了

下面就是代码实现:

/**
 * @Classname LRUCache4
 * @Description 正经哈希链表
 * @Date 2019/12/14 8:40
 * @Created by Jesse
 */
public class LRUCache4 {
    class Node{
        int key;
        int value;
        Node prev;
        Node next;

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

    private Node head;
    private Node tail;

    private HashMap<Integer,Node> map;
    private int capacity;

    public LRUCache4(int capacity) {
        map = new HashMap<>(capacity);
        this.capacity = capacity;
    }

    public int get(int key) {
        Node node = map.get(key);
        if (node == null) {
            return -1;
        }
        refreshNode(node);
        return node.value;
    }


    private void refreshNode(Node node) {
        if (node == tail) {
            return;
        }
        removeNode(node);
        addNode(node);
    }

    private void addNode(Node node) {
        if (tail == null) {
            head = node;
            tail = node;
        } else {
            tail.next = node;
            node.prev = tail;
            tail = tail.next;
        }
    }

    private void removeNode(Node node) {
        if (node == head && node == tail) {
            head = null;
            tail = null;
        } else if (node == tail) {
            tail = tail.prev;
            tail.next = null;
        } else if (node == head) {
            head = head.next;
            head.prev = null;
        } else {
            node.prev.next = node.next;
            node.next.prev = node.prev;
        }
    }

    public void put(int key, int value) {
        Node node = map.get(key);
        if (node == null) {
            if (map.size() >= capacity) {
                int removedKey = head.key;
                removeNode(head);
                map.remove(removedKey);
            }
            node = new Node(key, value);
            addNode(node);
            map.put(key, node);
        } else {
            node.value = value;
            refreshNode(node);
        }
    }
}

LeetCode传送门

LeetCode传送门

你可能感兴趣的:(数据结构与算法)