📜  为 LRU Cache 设计数据结构

📅  最后修改于: 2021-10-28 01:33:16             🧑  作者: Mango

为 LRU Cache 设计一个数据结构。它应该支持以下操作: getset

get(key) – 如果键存在于缓存中,则获取的值(将始终为正),否则返回 -1。
set(key, value) – 如果键不存在,则设置或插入值。当缓存达到其容量时,它应该在插入新项目之前使最近最少使用的项目无效。

例子:

问: Adobe、Hike 和更多公司。

解决方案:
1. 蛮力方法:
我们将保留一个节点数组,每个节点将包含以下信息:

C++
struct Node
{
    int key;
    int value;
 
    // It shows the time at which the key is stored.
    // We will use the timeStamp to find out the
    // least recently used (LRU) node.
    int timeStamp;
 
    Node(int _key, int _value)
    {
        key = _key;
        value = _value;
 
        // currentTimeStamp from system
        timeStamp = currentTimeStamp;
    }
};
 
// This code is contributed by subham348


Java
class Node {
    int key;
    int value;
 
    // it shows the time at which the key is stored.
    // We will use the timeStamp to find out the
    // least recently used (LRU) node.
    int timeStamp;
 
    public Node(int key, int value)
    {
        this.key = key;
        this.value = value;
 
        // currentTimeStamp from system
        this.timeStamp = currentTimeStamp;
    }
}


C++
#include 
using namespace std;
 
class LRUCache{
     
    public:
    class node
    {
        public:
        int key;
        int value;
        node * prev;
        node * next;
         
        node(int _key,int _value)
        {
            key = _key;
            value = _value;
        }
    };
     
    node* head = new node(-1, -1);
    node* tail = new node(-1, -1);
    int cap;
    map m;
     
    // Constructor for initializing the
    // cache capacity with the given value.
    LRUCache(int capacity)
    {
        cap = capacity;
        head->next = tail;
        tail->prev = head;
    }
     
    void addnode(node * temp)
    {
        node * dummy = head->next;
        head->next = temp;
        temp->prev = head;
        temp->next = dummy;
        dummy->prev = temp;
    }
     
    void deletenode(node * temp)
    {
        node * delnext = temp->next;
        node * delprev = temp->prev;
        delnext->prev = delprev;
        delprev->next = delnext;
    }
     
    // This method works in O(1)
    int get(int key)
    {
        if (m.find(key) != m.end())
        {
            node * res =  m[key];
            m.erase(key);
            int ans = res->value;
            deletenode(res);
            addnode(res);
            m[key] = head->next;
           cout << "Got the value : " << ans
                << " for the key: " << key << "\n";
            return ans;
        }
        cout << "Did not get any value for the key: "
             << key << "\n";
        return -1;
    }
     
    // This method works in O(1)
    void set(int key, int value)
    {
         
        cout << "Going to set the (key, value) : ("
             << key << ", " << value << ")" << "\n";
        if (m.find(key) != m.end())
        {
            node * exist = m[key];
            m.erase(key);
            deletenode(exist);
        }
         
        if (m.size() == cap)
        {
            m.erase(tail->prev->key);
            deletenode(tail->prev);
        }
        addnode(new node(key, value));
        m[key] = head->next;
    }
};
 
// Driver code
int main()
{
    cout << "Going to test the LRU  "
         << "Cache Implementation\n";
          
    LRUCache * cache = new LRUCache(2);
 
    // It will store a key (1) with value
    // 10 in the cache.
    cache->set(1, 10);
 
    // It will store a key (1) with value 10 in the
    // cache.
    cache->set(2, 20);
    cout << "Value for the key: 1 is "
         << cache->get(1) << "\n"; // returns 10
 
    // Evicts key 2 and store a key (3) with
    // value 30 in the cache.
    cache->set(3, 30);
 
    cout << "Value for the key: 2 is "
         << cache->get(2) << "\n"; // returns -1 (not found)
 
    // Evicts key 1 and store a key (4) with
    // value 40 in the cache.
    cache->set(4, 40);
    cout << "Value for the key: 1 is "
         << cache->get(1) << "\n"; // returns -1 (not found)
    cout << "Value for the key: 3 is "
         << cache->get(3) << "\n"; // returns 30
    cout << "Value for the key: 4 is "
         << cache->get(4) << "\n"; // return 40
 
    return 0;
}
 
// This code is contributed by CoderSaty


Java
import java.util.HashMap;
 
class Node {
    int key;
    int value;
    Node pre;
    Node next;
 
    public Node(int key, int value)
    {
        this.key = key;
        this.value = value;
    }
}
 
class LRUCache {
    private HashMap map;
    private int capacity, count;
    private Node head, tail;
 
    public LRUCache(int capacity)
    {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head.next = tail;
        tail.pre = head;
        head.pre = null;
        tail.next = null;
        count = 0;
    }
 
    public void deleteNode(Node node)
    {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }
 
    public void addToHead(Node node)
    {
        node.next = head.next;
        node.next.pre = node;
        node.pre = head;
        head.next = node;
    }
 
    // This method works in O(1)
    public int get(int key)
    {
        if (map.get(key) != null) {
            Node node = map.get(key);
            int result = node.value;
            deleteNode(node);
            addToHead(node);
            System.out.println("Got the value : " + result
                               + " for the key: " + key);
            return result;
        }
        System.out.println("Did not get any value"
                           + " for the key: " + key);
        return -1;
    }
 
    // This method works in O(1)
    public void set(int key, int value)
    {
        System.out.println("Going to set the (key, "
                           + "value) : (" + key + ", "
                           + value + ")");
        if (map.get(key) != null) {
            Node node = map.get(key);
            node.value = value;
            deleteNode(node);
            addToHead(node);
        }
        else {
            Node node = new Node(key, value);
            map.put(key, node);
            if (count < capacity) {
                count++;
                addToHead(node);
            }
            else {
                map.remove(tail.pre.key);
                deleteNode(tail.pre);
                addToHead(node);
            }
        }
    }
}
 
public class TestLRUCache {
    public static void main(String[] args)
    {
        System.out.println("Going to test the LRU "
                           + " Cache Implementation");
        LRUCache cache = new LRUCache(2);
 
        // it will store a key (1) with value
        // 10 in the cache.
        cache.set(1, 10);
 
        // it will store a key (1) with value 10 in the
        // cache.
        cache.set(2, 20);
        System.out.println("Value for the key: 1 is "
                           + cache.get(1)); // returns 10
 
        // evicts key 2 and store a key (3) with
        // value 30 in the cache.
        cache.set(3, 30);
 
        System.out.println(
            "Value for the key: 2 is "
            + cache.get(2)); // returns -1 (not found)
 
        // evicts key 1 and store a key (4) with
        // value 40 in the cache.
        cache.set(4, 40);
        System.out.println(
            "Value for the key: 1 is "
            + cache.get(1)); // returns -1 (not found)
        System.out.println("Value for the key: 3 is "
                           + cache.get(3)); // returns 30
        System.out.println("Value for the key: 4 is "
                           + cache.get(4)); // return 40
    }
}


Python3
# Class for a Doubly LinkedList Node
class DLLNode:
    def __init__(self, key, val):
        self.val = val
        self.key = key
        self.prev = None
        self.next = None
 
# LRU cache class
class LRUCache:
 
    def __init__(self, capacity):
        # capacity:  capacity of cache
        # Initialize all variable
        self.capacity = capacity
        self.map = {}
        self.head = DLLNode(0, 0)
        self.tail = DLLNode(0, 0)
        self.head.next = self.tail
        self.tail.prev = self.head
        self.count = 0
 
    def deleteNode(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
 
    def addToHead(self, node):
        node.next = self.head.next
        node.next.prev = node
        node.prev = self.head
        self.head.next = node
 
    # This method works in O(1)
    def get(self, key):
        if key in self.map:
            node = self.map[key]
            result = node.val
            self.deleteNode(node)
            self.addToHead(node)
            print('Got the value : {} for the key: {}'.format(result, key))
            return result
        print('Did not get any value for the key: {}'.format(key))
        return -1
 
    # This method works in O(1)
    def set(self, key, value):
        print('going to set the (key, value) : ( {}, {})'.format(key, value))
        if key in self.map:
            node = self.map[key]
            node.val = value
            self.deleteNode(node)
            self.addToHead(node)
        else:
            node = DLLNode(key, value)
            self.map[key] = node
            if self.count < self.capacity:
                self.count += 1
                self.addToHead(node)
            else:
                del self.map[self.tail.prev.key]
                self.deleteNode(self.tail.prev)
                self.addToHead(node)
 
 
if __name__ == '__main__':
    print('Going to test the LRU Cache Implementation')
    cache = LRUCache(2)
 
    # it will store a key (1) with value
    # 10 in the cache.
    cache.set(1, 10)
 
    # it will store a key (1) with value 10 in the cache.
    cache.set(2, 20)
    print('Value for the key: 1 is {}'.format(cache.get(1)))  # returns 10
 
    # evicts key 2 and store a key (3) with
    # value 30 in the cache.
    cache.set(3, 30)
 
    print('Value for the key: 2 is {}'.format(
        cache.get(2)))  # returns -1 (not found)
 
    # evicts key 1 and store a key (4) with
    # value 40 in the cache.
    cache.set(4, 40)
    print('Value for the key: 1 is {}'.format(
        cache.get(1)))  # returns -1 (not found)
    print('Value for the key: 3 is {}'.format(cache.get(3)))  # returns 30
    print('Value for the key: 4 is {}'.format(cache.get(4)))  # returns 40


Java
import java.util.LinkedHashMap;
import java.util.Map;
 
class LRUCache {
    private LinkedHashMap map;
    private final int CAPACITY;
    public LRUCache(int capacity)
    {
        CAPACITY = capacity;
        map = new LinkedHashMap(capacity, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry eldest)
            {
                return size() > CAPACITY;
            }
        };
    }
 
    // This method works in O(1)
    public int get(int key)
    {
        System.out.println("Going to get the value " +
                               "for the key : " + key);
        return map.getOrDefault(key, -1);
    }
 
    // This method works in O(1)
    public void set(int key, int value)
    {
        System.out.println("Going to set the (key, " +
             "value) : (" + key + ", " + value + ")");
        map.put(key, value);
    }
}
 
public class TestLRUCacheWithLinkedHashMap {
 
    public static void main(String[] args)
    {
        System.out.println("Going to test the LRU "+
                           " Cache Implementation");
        LRUCache cache = new LRUCache(2);
  
        // it will store a key (1) with value
        // 10 in the cache.
        cache.set(1, 10);
 
        // it will store a key (1) with value 10 in the cache.
        cache.set(2, 20);
        System.out.println("Value for the key: 1 is " +
                           cache.get(1)); // returns 10
 
        // evicts key 2 and store a key (3) with
        // value 30 in the cache.
        cache.set(3, 30);
 
        System.out.println("Value for the key: 2 is " +
                cache.get(2)); // returns -1 (not found)
 
        // evicts key 1 and store a key (4) with
        // value 40 in the cache.
        cache.set(4, 40);
        System.out.println("Value for the key: 1 is " +
               cache.get(1)); // returns -1 (not found)
        System.out.println("Value for the key: 3 is " +
                           cache.get(3)); // returns 30
        System.out.println("Value for the key: 4 is " +
                           cache.get(4)); // return 40
 
    }
}


数组的大小将等于给定的缓存容量。

(a)对于get(int key):我们可以简单地遍历数组并将每个节点的键与给定的键进行比较,并返回存储在该键的节点中的值。如果我们没有找到任何这样的节点,只需返回 -1。
时间复杂度: O(n)

(b) For set(int key, int value):如果数组已满,我们必须从数组中删除一个节点。为了找到 LRU 节点,我们将遍历数组并找到具有最小 timeStamp 值的节点。我们将简单地在 LRU 节点的位置插入新节点(具有新的键和值)。
如果数组未满,我们可以简单地在数组中的最后一个当前索引处插入一个新节点。
时间复杂度: O(n)

2.优化方法:
解决这个问题的关键是使用双链表,它使我们能够快速移动节点。
LRU 缓存是键和双链接节点的哈希映射。哈希映射使 get() 的时间为 O(1)。双链接节点列表使节点添加/删除操作 O(1)。

使用双向链表和 HashMap 的代码:

C++

#include 
using namespace std;
 
class LRUCache{
     
    public:
    class node
    {
        public:
        int key;
        int value;
        node * prev;
        node * next;
         
        node(int _key,int _value)
        {
            key = _key;
            value = _value;
        }
    };
     
    node* head = new node(-1, -1);
    node* tail = new node(-1, -1);
    int cap;
    map m;
     
    // Constructor for initializing the
    // cache capacity with the given value.
    LRUCache(int capacity)
    {
        cap = capacity;
        head->next = tail;
        tail->prev = head;
    }
     
    void addnode(node * temp)
    {
        node * dummy = head->next;
        head->next = temp;
        temp->prev = head;
        temp->next = dummy;
        dummy->prev = temp;
    }
     
    void deletenode(node * temp)
    {
        node * delnext = temp->next;
        node * delprev = temp->prev;
        delnext->prev = delprev;
        delprev->next = delnext;
    }
     
    // This method works in O(1)
    int get(int key)
    {
        if (m.find(key) != m.end())
        {
            node * res =  m[key];
            m.erase(key);
            int ans = res->value;
            deletenode(res);
            addnode(res);
            m[key] = head->next;
           cout << "Got the value : " << ans
                << " for the key: " << key << "\n";
            return ans;
        }
        cout << "Did not get any value for the key: "
             << key << "\n";
        return -1;
    }
     
    // This method works in O(1)
    void set(int key, int value)
    {
         
        cout << "Going to set the (key, value) : ("
             << key << ", " << value << ")" << "\n";
        if (m.find(key) != m.end())
        {
            node * exist = m[key];
            m.erase(key);
            deletenode(exist);
        }
         
        if (m.size() == cap)
        {
            m.erase(tail->prev->key);
            deletenode(tail->prev);
        }
        addnode(new node(key, value));
        m[key] = head->next;
    }
};
 
// Driver code
int main()
{
    cout << "Going to test the LRU  "
         << "Cache Implementation\n";
          
    LRUCache * cache = new LRUCache(2);
 
    // It will store a key (1) with value
    // 10 in the cache.
    cache->set(1, 10);
 
    // It will store a key (1) with value 10 in the
    // cache.
    cache->set(2, 20);
    cout << "Value for the key: 1 is "
         << cache->get(1) << "\n"; // returns 10
 
    // Evicts key 2 and store a key (3) with
    // value 30 in the cache.
    cache->set(3, 30);
 
    cout << "Value for the key: 2 is "
         << cache->get(2) << "\n"; // returns -1 (not found)
 
    // Evicts key 1 and store a key (4) with
    // value 40 in the cache.
    cache->set(4, 40);
    cout << "Value for the key: 1 is "
         << cache->get(1) << "\n"; // returns -1 (not found)
    cout << "Value for the key: 3 is "
         << cache->get(3) << "\n"; // returns 30
    cout << "Value for the key: 4 is "
         << cache->get(4) << "\n"; // return 40
 
    return 0;
}
 
// This code is contributed by CoderSaty

Java

import java.util.HashMap;
 
class Node {
    int key;
    int value;
    Node pre;
    Node next;
 
    public Node(int key, int value)
    {
        this.key = key;
        this.value = value;
    }
}
 
class LRUCache {
    private HashMap map;
    private int capacity, count;
    private Node head, tail;
 
    public LRUCache(int capacity)
    {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new Node(0, 0);
        tail = new Node(0, 0);
        head.next = tail;
        tail.pre = head;
        head.pre = null;
        tail.next = null;
        count = 0;
    }
 
    public void deleteNode(Node node)
    {
        node.pre.next = node.next;
        node.next.pre = node.pre;
    }
 
    public void addToHead(Node node)
    {
        node.next = head.next;
        node.next.pre = node;
        node.pre = head;
        head.next = node;
    }
 
    // This method works in O(1)
    public int get(int key)
    {
        if (map.get(key) != null) {
            Node node = map.get(key);
            int result = node.value;
            deleteNode(node);
            addToHead(node);
            System.out.println("Got the value : " + result
                               + " for the key: " + key);
            return result;
        }
        System.out.println("Did not get any value"
                           + " for the key: " + key);
        return -1;
    }
 
    // This method works in O(1)
    public void set(int key, int value)
    {
        System.out.println("Going to set the (key, "
                           + "value) : (" + key + ", "
                           + value + ")");
        if (map.get(key) != null) {
            Node node = map.get(key);
            node.value = value;
            deleteNode(node);
            addToHead(node);
        }
        else {
            Node node = new Node(key, value);
            map.put(key, node);
            if (count < capacity) {
                count++;
                addToHead(node);
            }
            else {
                map.remove(tail.pre.key);
                deleteNode(tail.pre);
                addToHead(node);
            }
        }
    }
}
 
public class TestLRUCache {
    public static void main(String[] args)
    {
        System.out.println("Going to test the LRU "
                           + " Cache Implementation");
        LRUCache cache = new LRUCache(2);
 
        // it will store a key (1) with value
        // 10 in the cache.
        cache.set(1, 10);
 
        // it will store a key (1) with value 10 in the
        // cache.
        cache.set(2, 20);
        System.out.println("Value for the key: 1 is "
                           + cache.get(1)); // returns 10
 
        // evicts key 2 and store a key (3) with
        // value 30 in the cache.
        cache.set(3, 30);
 
        System.out.println(
            "Value for the key: 2 is "
            + cache.get(2)); // returns -1 (not found)
 
        // evicts key 1 and store a key (4) with
        // value 40 in the cache.
        cache.set(4, 40);
        System.out.println(
            "Value for the key: 1 is "
            + cache.get(1)); // returns -1 (not found)
        System.out.println("Value for the key: 3 is "
                           + cache.get(3)); // returns 30
        System.out.println("Value for the key: 4 is "
                           + cache.get(4)); // return 40
    }
}

蟒蛇3

# Class for a Doubly LinkedList Node
class DLLNode:
    def __init__(self, key, val):
        self.val = val
        self.key = key
        self.prev = None
        self.next = None
 
# LRU cache class
class LRUCache:
 
    def __init__(self, capacity):
        # capacity:  capacity of cache
        # Initialize all variable
        self.capacity = capacity
        self.map = {}
        self.head = DLLNode(0, 0)
        self.tail = DLLNode(0, 0)
        self.head.next = self.tail
        self.tail.prev = self.head
        self.count = 0
 
    def deleteNode(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
 
    def addToHead(self, node):
        node.next = self.head.next
        node.next.prev = node
        node.prev = self.head
        self.head.next = node
 
    # This method works in O(1)
    def get(self, key):
        if key in self.map:
            node = self.map[key]
            result = node.val
            self.deleteNode(node)
            self.addToHead(node)
            print('Got the value : {} for the key: {}'.format(result, key))
            return result
        print('Did not get any value for the key: {}'.format(key))
        return -1
 
    # This method works in O(1)
    def set(self, key, value):
        print('going to set the (key, value) : ( {}, {})'.format(key, value))
        if key in self.map:
            node = self.map[key]
            node.val = value
            self.deleteNode(node)
            self.addToHead(node)
        else:
            node = DLLNode(key, value)
            self.map[key] = node
            if self.count < self.capacity:
                self.count += 1
                self.addToHead(node)
            else:
                del self.map[self.tail.prev.key]
                self.deleteNode(self.tail.prev)
                self.addToHead(node)
 
 
if __name__ == '__main__':
    print('Going to test the LRU Cache Implementation')
    cache = LRUCache(2)
 
    # it will store a key (1) with value
    # 10 in the cache.
    cache.set(1, 10)
 
    # it will store a key (1) with value 10 in the cache.
    cache.set(2, 20)
    print('Value for the key: 1 is {}'.format(cache.get(1)))  # returns 10
 
    # evicts key 2 and store a key (3) with
    # value 30 in the cache.
    cache.set(3, 30)
 
    print('Value for the key: 2 is {}'.format(
        cache.get(2)))  # returns -1 (not found)
 
    # evicts key 1 and store a key (4) with
    # value 40 in the cache.
    cache.set(4, 40)
    print('Value for the key: 1 is {}'.format(
        cache.get(1)))  # returns -1 (not found)
    print('Value for the key: 3 is {}'.format(cache.get(3)))  # returns 30
    print('Value for the key: 4 is {}'.format(cache.get(4)))  # returns 40
输出:
Going to test the LRU  Cache Implementation
Going to set the (key, value) : (1, 10)
Going to set the (key, value) : (2, 20)
Got the value : 10 for the key: 1
Value for the key: 1 is 10
Going to set the (key, value) : (3, 30)
Did not get any value for the key: 2
Value for the key: 2 is -1
Going to set the (key, value) : (4, 40)
Did not get any value for the key: 1
Value for the key: 1 is -1
Got the value : 30 for the key: 3
Value for the key: 3 is 30
Got the value : 40 for the key: 4
Value for the key: 4 is 40

Java使用 LinkedHashMap 的另一种实现:

removeEldestEntry () 被覆盖以在大小超出容量时强制执行删除旧映射的策略。

Java

import java.util.LinkedHashMap;
import java.util.Map;
 
class LRUCache {
    private LinkedHashMap map;
    private final int CAPACITY;
    public LRUCache(int capacity)
    {
        CAPACITY = capacity;
        map = new LinkedHashMap(capacity, 0.75f, true) {
            protected boolean removeEldestEntry(Map.Entry eldest)
            {
                return size() > CAPACITY;
            }
        };
    }
 
    // This method works in O(1)
    public int get(int key)
    {
        System.out.println("Going to get the value " +
                               "for the key : " + key);
        return map.getOrDefault(key, -1);
    }
 
    // This method works in O(1)
    public void set(int key, int value)
    {
        System.out.println("Going to set the (key, " +
             "value) : (" + key + ", " + value + ")");
        map.put(key, value);
    }
}
 
public class TestLRUCacheWithLinkedHashMap {
 
    public static void main(String[] args)
    {
        System.out.println("Going to test the LRU "+
                           " Cache Implementation");
        LRUCache cache = new LRUCache(2);
  
        // it will store a key (1) with value
        // 10 in the cache.
        cache.set(1, 10);
 
        // it will store a key (1) with value 10 in the cache.
        cache.set(2, 20);
        System.out.println("Value for the key: 1 is " +
                           cache.get(1)); // returns 10
 
        // evicts key 2 and store a key (3) with
        // value 30 in the cache.
        cache.set(3, 30);
 
        System.out.println("Value for the key: 2 is " +
                cache.get(2)); // returns -1 (not found)
 
        // evicts key 1 and store a key (4) with
        // value 40 in the cache.
        cache.set(4, 40);
        System.out.println("Value for the key: 1 is " +
               cache.get(1)); // returns -1 (not found)
        System.out.println("Value for the key: 3 is " +
                           cache.get(3)); // returns 30
        System.out.println("Value for the key: 4 is " +
                           cache.get(4)); // return 40
 
    }
}
输出:
Going to test the LRU  Cache Implementation
Going to set the (key, value) : (1, 10)
Going to set the (key, value) : (2, 20)
Going to get the value for the key : 1
Value for the key: 1 is 10
Going to set the (key, value) : (3, 30)
Going to get the value for the key : 2
Value for the key: 2 is -1
Going to set the (key, value) : (4, 40)
Going to get the value for the key : 1
Value for the key: 1 is -1
Going to get the value for the key : 3
Value for the key: 3 is 30
Going to get the value for the key : 4
Value for the key: 4 is 40

你可以在这里找到 C++ 代码

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程