LRU cache簡易實現

1. 什麼是LRU cache?

就是一種緩存,算法思想是:將最近訪問的數據挪動到頭部,如果下次還是訪問這個數據,那麼就能在靠前的位置訪問到(緩存大小固定,需要淘汰最近最少訪問數據)
這點其實是運用了時間局部性原理:最近一次訪問的位置,下一次也很可能訪問

2. 實現思路:

  1. 使用雙向鏈表
  2. 使用哨兵節點,pre 指向尾節點,next 指向頭結點
  3. 插入操作:push(1,先查找,如果有,挪動到頭部2,如果沒有,檢查cache 是否到達大容量,如果達到了,刪除尾節點,將新節點插入到頭結點)
  4. 查找操作:如果沒找到,返回null,如果找到,先將目標節點調整到鏈表頭,然後返回該節點

3. 數據舉例說明:

假設輸入序列是 1,3,2,3,4
輸入完1,鏈表效果是
1->哨兵->1
輸入完3,效果是:(使用頭插法,新的元素總是在頭結點)
1 ->哨兵->3->1
輸入2,鏈表效果:
1 ->哨兵->2->3->1
輸入3:(注意cache 中已經存在3,只需要將該節點調整到頭結點即可)
1 ->哨兵->3->2->1
輸入4:注意,cache 大小已經達到最大緩存限制,而且 cache 不存在4,那麼需要線刪除尾節點(也就是1,然後將4插入到頭部)
2->哨兵->4->3->2

4. java 代碼實現:


import java.util.Scanner;

/**
 * @author wangwei
 * @date 2019/3/28 21:30
 * @classDescription 最近最少使用, 放在鏈表末尾
 * 使用雙向鏈表:
 * 1,pop (查找元素,將目標元素放到鏈表頭部,然後返回數據)
 * 2,push 添加元素,檢查size,size如果超過最大,移除最後一個節點,將新節點插入鏈表頭部
 */
public class LRUCache<T> {
    private int MAX_SIZE;// cache 最大容量
    private int size;
    private DoNode sentryNode;// 虛擬節點,pre 指向頭節點,next 指向尾節點

    public LRUCache(int MAX_SIZE) {
        this.MAX_SIZE = MAX_SIZE;
        sentryNode = new DoNode(Integer.MIN_VALUE);
        sentryNode.next = sentryNode;
        sentryNode.pre = sentryNode;

    }

    // 查找數據,並將節點返回
    public DoNode<T> pop(T data) {
        DoNode target = search(data);
        if (null == target) {
            return null;
        }
        //將target 調整到頭部
        moveExistsNodeToHead(target);
        return target;
    }

    public void push(T data) {

        DoNode lookResult = search(data);
        if (lookResult != null) {
            moveExistsNodeToHead(lookResult);
            return;
        }


        if (size >= MAX_SIZE) {
            removeLast();
        }
        // 插入新節點
        insertBeforeHead(data);
    }

    private DoNode<T> search(T data) {
        DoNode result = sentryNode.next;
        // 不要繞圈查找,如果到了哨兵還沒查找到,說明緩存不存在
        while (result != null && (!result.equals(sentryNode))) {
            if (result.data.equals(data)) {
                break;
            }
            result=result.next;
        }
        return sentryNode.equals(result) ? null : result;
    }

    //將node 調整緩存中存在的節點到頭部
    private void moveExistsNodeToHead(DoNode target) {
        //將target 調整到頭部
        target.pre.next = target.next;
        target.next=target.pre;

        target.pre = sentryNode;
        sentryNode.next.pre=target;
        target.next=sentryNode.next;
        sentryNode.next = target;
    }

    //刪除尾節點
    private void removeLast() {
        if (size <= 0) {
            throw new RuntimeException(" 不能繼續刪除尾節點");
        }
        sentryNode.pre = sentryNode.pre.pre;
        sentryNode.pre.next = sentryNode;
        size--;
    }

    // 頭插入節點
    private void insertBeforeHead(T data) {
        DoNode node = new DoNode(data);
        node.pre = sentryNode;
        node.next = sentryNode.next;
        node.next.pre=node;
        sentryNode.next = node;

        size++;
    }

    public static void main(String[] args) {
        LRUCache<Integer> cache = new LRUCache<>(3);
        Scanner in = new Scanner(System.in);
        int [] input={1,2,3,2,3};
        for(int data:input){
            cache.push(data);
            // System.out.println("當前cache:" + cache.toString());

        }



    }


    class DoNode<T> {
        T data;
        DoNode pre;
        DoNode next;

        public DoNode(T data) {
            this.data = data;
        }


    }
}

5. 分析:

  1. 爲什麼使用雙向鏈表?
    方便刪除節點,以及插入節點
  2. 爲什麼使用哨兵
    方便快速定位頭結點與尾節點(插入是在頭結點,刪除是在尾節點)

6另一種更通用的版本實現:

將節點定義由單獨的data改爲 K+DATA的

package top.forethought.linklist;

import java.util.Scanner;

/**
 * @author wangwei
 * @date 2019/3/28 21:30
 * @classDescription 最近最少使用, 放在鏈表末尾
 * 使用雙向鏈表:
 * 1,pop (查找元素,將目標元素放到鏈表頭部,然後返回數據)
 * 2,push 添加元素,檢查size,size如果超過最大,移除最後一個節點,將新節點插入鏈表頭部
 */
public class LRUCache<K,V> {
    private int MAX_SIZE;// cache 最大容量
    private int size;
    private DoNode sentryNode;// 虛擬節點,pre 指向頭節點,next 指向尾節點

    public LRUCache(int MAX_SIZE) {
        this.MAX_SIZE = MAX_SIZE;
        sentryNode = new DoNode();
        sentryNode.next = sentryNode;
        sentryNode.pre = sentryNode;

    }

    // 查找數據,並將節點返回
    public DoNode<K,V> pop(K key) {
        DoNode target = search(key);
        if (null == target) {
            return null;
        }
        //將target 調整到頭部
        moveExistsNodeToHead(target);
        return target;
    }

    public void push(K key,V data) {

        DoNode lookResult = search(key);
        if (lookResult != null) {
            moveExistsNodeToHead(lookResult);
            return;
        }


        if (size >= MAX_SIZE) {
            removeLast();
        }
        // 插入新節點
        insertBeforeHead(key,data);
    }

    private DoNode search(K key) {
        DoNode result = sentryNode.next;
        // 不要繞圈查找,如果到了哨兵還沒查找到,說明緩存不存在
        while (result != null && (!result.equals(sentryNode))) {
            if (key.equals(result.key)) {
                break;
            }
            result = result.next;
        }
        return sentryNode.equals(result) ? null : result;
    }

    //將node 調整緩存中存在的節點到頭部
    private void moveExistsNodeToHead(DoNode target) {
        //將target 調整到頭部
        target.pre.next = target.next;
        target.next = target.pre;

        target.pre = sentryNode;
        sentryNode.next.pre = target;
        target.next = sentryNode.next;
        sentryNode.next = target;
    }

    //刪除尾節點
    private void removeLast() {
        if (size <= 0) {
            throw new RuntimeException(" 不能繼續刪除尾節點");
        }
        sentryNode.pre = sentryNode.pre.pre;
        sentryNode.pre.next = sentryNode;
        size--;
    }

    // 頭插入節點
    private void insertBeforeHead(K key,V data) {
        DoNode node = new DoNode(key,data);
        node.pre = sentryNode;
        node.next = sentryNode.next;
        node.next.pre = node;
        sentryNode.next = node;

        size++;
    }

    public static void main(String[] args) {
        LRUCache<Integer,Integer> cache = new LRUCache<>(3);
        Scanner in = new Scanner(System.in);
        Integer[] input = {1, 2, 3, 2, 3};
        for (Integer data : input) {
            Integer key=data-1;
            cache.push(key,data);
        }


    }


    class DoNode<K,V> {
        K key;
        V data;
        DoNode pre;
        DoNode next;

        public DoNode(K key,V data) {
            this.key=key;
            this.data = data;
        }

        public DoNode() {
        }
    }
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章