LRU algorithm and LFU algorithm

Posted by Kyori on Sun, 23 Jan 2022 17:26:43 +0100

LRU algorithm

LRU is the abbreviation of Least Recently Used, that is, the Least Recently Used algorithm. It is a page replacement algorithm in the operating system. Because the number of pages stored in memory is limited, the recently unused algorithm will be moved out of memory.
LRU principle:
The principle of LRU is shown in the figure below:

It will put the most recently accessed nodes in front and the nodes that have not been accessed recently in the back. If the cache space is full, the last node will be deleted and a new node will be added to the header.

Java implementation
For the linked storage structure, the collections used include ArrayList and LinkedList. The bottom layer of ArrayList is based on array, and the time complexity of moving elements is high. Therefore, LinkedList is used, which is based on the form of linked list and is a two-way linked list, because nodes should be deleted. The search time complexity of linked list is also high, but it can be located quickly by using hash. Therefore, LRU algorithm can be implemented through bidirectional linked list and hash.

Code part
Manual implementation of two-way linked list:

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

public class LRUCache {
    //Define map mapping
    private Map<String, Node> map;
    //Define cache capacity
    private int capacity = 8;
    //There is currently a data in the cache
    private int size = 0;
    //Head node and tail node of linked list
    private Node head, tail;

    //Read data from cache
    public int get(String key) {
        Node node = map.get(key);
        //If it can be found, move the node to the head of the linked list without returning - 1
        if (node != null) {
            moveToHead(node);
            return node.value;
        }
        return -1;
    }

    //Add node to cache
    public void put(String key, int value) {
    	if(capacity == 0)   return;
        Node node = map.get(key);
        if (node != null) {
            //If it exists, modify the value of this point and move it to the head of the linked list
            node.value = value;
            moveToHead(node);
            return;
        }

        //If it does not exist, first judge whether the cache space is sufficient
        if (size == capacity) {
            map.remove(tail.pre.key);
            deleteNode(tail.pre);
            size -= 1;
        }
        Node newNode = new Node(key, value);
        addToHead(newNode);
        map.put(key, newNode);
        size += 1;
    }

    //Move node to head node
    private void moveToHead(Node node) {
        deleteNode(node);
        addToHead(node);
    }

    //Add node to header
    private void addToHead(Node node) {
        node.next = head.next;
        head.next.pre = node;
        node.pre = head;
        head.next = node;
    }

    //Delete this node
    private void deleteNode(Node node) {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }

    public LRUCache(int capacity) {
        this.capacity = capacity;
        initLinkedList();
        map = new HashMap<>(capacity);
        size = 0;
    }

    //Initialize linked list
    private void initLinkedList() {
        head = new Node();
        tail = new Node();
        head.next = tail;
        tail.pre = head;
    }

    public LRUCache() {
        initLinkedList();
        map = new HashMap<>(this.capacity);
    }

    public static class Node {
        public Node pre;
        public Node next;

        public String key;
        public int value;

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

        public Node() {
        }
    }

    public static void main(String[] args) {
        LRUCache cache = new LRUCache(2);
        cache.put("key1", 1);
        cache.put("key2", 2);
        System.out.println(cache.get("key2"));//21
        cache.put("key3", 3);
        System.out.println(cache.get("key1"));//32
    }

}

Call the LinkedList API for implementation

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

public class LRUCache {
    //Define map mapping
    private Map<String, Node> map;
    //Define cache capacity
    private int capacity = 8;

    private LinkedList<Node> list = new LinkedList<>();

    //Read data from cache
    public int get(String key) {
        Node node = map.get(key);
        //If it can be found, move the node to the head of the linked list without returning - 1
        if (node != null) {
            list.remove(node);
            list.addFirst(node);
            return node.value;
        }
        return -1;
    }

    //Add node to cache
    public void put(String key, int value) {
        if(capacity == 0)   return;
        Node node = map.get(key);
        if (node != null) {
            //If it exists, modify the value of this point and move it to the head of the linked list
            node.value = value;
            list.remove(node);
            list.addFirst(node);
            return;
        }

        //If it does not exist, first judge whether the cache space is sufficient
        if (list.size() == capacity) {
            //If it exists, delete the in the map and LinkedList
            map.remove(list.getLast().key);
            list.removeLast();
        }
        Node newNode = new Node(key, value);
        map.put(key, newNode);
        list.addFirst(newNode);
    }


    public LRUCache(int capacity) {
        this.capacity = capacity;
        //It also defines the capacity of the map. In fact, this step is not necessary
        map = new HashMap<>(capacity);
    }


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

    public static class Node {
        public String key;
        public int value;

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

        public Node() {
        }
    }

    public static void main(String[] args) {
        LRUCache cache = new LRUCache(2);
        cache.put("key1",1);
        cache.put("key2",2);
        System.out.println(cache.get("key2"));  //21
        cache.put("key3",3);
        System.out.println(cache.get("key1"));  //32
    }

}

LFU algorithm

LFU is the abbreviation of Least Frequently Used, that is, the least recent access frequency. It is also a page exchange algorithm in the operating system. It calls out the pages that have been used least recently and have not been used for a long time.

LFU principle

Among them, one map stores the relationship between frequency and the collection of nodes, that is, freq - list, and the other map stores the relationship between key - node. Read the node from the key - node, and then go to freq - list to add the node to the corresponding list set

Code part
Since a linked list must be created for each frequency to store, it is implemented directly using the API in Java

package cache;

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

public class LFUCache {

    private Map<String,Node> key_node;
    private Map<Integer, LinkedList<Node>> freq_list;
    private int capacity = 2;

    private int minFreq = 1;


    public int get(String key){
        //If this number is not included, return - 1
        if(!key_node.containsKey(key))  return -1;
        Node node = key_node.get(key);
        //Get the frequency of the node
        int freq = node.freq;
        //Current node frequency plus 1
        node.freq += 1;
        //Deletes a node from the corresponding collection
        freq_list.get(freq).remove(node);
        //If the deleted node is 0, delete the collection
        if(freq_list.get(freq).size() == 0){
            freq_list.remove(freq);
            if(freq == minFreq){
                minFreq += 1;
            }
        }
        LinkedList<Node> list = freq_list.getOrDefault(freq + 1, new LinkedList<Node>());
        list.addFirst(node);
        freq_list.put(freq + 1, list);
        return node.value;
    }


    public void put(String key,int value){
        if(capacity == 0)   return ;
        Node node = key_node.get(key);
        if(node != null){
            //Gets the frequency of the current node
            int freq = node.freq;
            node.freq += 1;
            node.value = value;
            //Deletes a node from the set of corresponding frequency nodes
            freq_list.get(freq).remove(node);
            //If there are no elements in the collection, the collection is deleted
            if(freq_list.get(freq).size() == 0){
                freq_list.remove(freq);
                //If the minimum frequency is deleted, add 1 to the minimum frequency
                if(freq == minFreq){
                    minFreq += 1;
                }
            }
            //Move the node to the node set of the next frequency
            LinkedList<Node> list = freq_list.getOrDefault(freq + 1, new LinkedList<Node>());
            list.offerFirst(node);
            freq_list.put(freq + 1, list);
            key_node.put(key, node);
        }else {
            Node newNode = new Node(key,value,1);
            //If the cache capacity is full, you need to delete the data that has been used the least frequently and has not been used for a long time
            if(key_node.size() == capacity){
                Node lastNode = freq_list.get(minFreq).pollLast();
                key_node.remove(lastNode.key);
                if(freq_list.get(minFreq).size() == 0){
                    freq_list.remove(minFreq);
                }
            }
            //Add a new node to the collection
            key_node.put(key,newNode);
            LinkedList<Node> list = freq_list.getOrDefault(1, new LinkedList<Node>());
            list.offerFirst(newNode);
            freq_list.put(1,list);
            minFreq = 1;
        }
    }

    public LFUCache(int capacity) {
        this.capacity = capacity;
        key_node = new HashMap<>();
        freq_list = new HashMap<>();
    }

    public LFUCache() {
        key_node = new HashMap<>();
        freq_list = new HashMap<>();
    }

    public static class Node{
        public String key;
        public int value;
        public int freq;

        public Node() {
        }

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

    public static void main(String[] args) {
        LFUCache cache = new LFUCache(2);
        cache.put("key1",1);    //1
        cache.put("key2",2);    //21
        cache.put("key3",3);    //32
        System.out.println(cache.get("key1"));
        System.out.println(cache.get("key2"));
        System.out.println(cache.get("key3"));
        cache.put("key1",1);    //13
        System.out.println(cache.get("key1"));
        System.out.println(cache.get("key2"));
        System.out.println(cache.get("key3"));
        cache.put("key2",2);    //21
        cache.put("key1",1);    //12
    }
}

Topics: Java Algorithm linked list