📜  LRU 完整表格(1)

📅  最后修改于: 2023-12-03 15:32:45.537000             🧑  作者: Mango

LRU 完整表格

LRU,Least Recently Used,最近最少使用算法,是一种用于缓存管理的算法。在每个缓存块上分配一个最近使用的时间戳,每次访问缓存块时,更新时间戳,当缓存块块已满时,将最久未使用的块替换掉。LRU 完整表格是指利用哈希表和双向链表实现的 LRU 算法,用于解决面试中的 LRU 缓存问题。

实现

在实现 LRU 完整表格前,需要先了解哈希表和双向链表的概念。

哈希表

哈希表,也被称为散列表,是根据关键码值(Key-Value)而直接进行访问的数据结构。

哈希表使用哈希函数将关键码值映射到表中的位置,以加快查找的速度。

哈希函数的设计非常重要,一个好的哈希函数应该将不同的关键码值映射到不同的位置,减少哈希冲突的可能性。

下面是一个简单的哈希表的实现:

class HashTable {
  constructor() {
    this.table = new Array(10);
  }

  put(key, value) {
    const hash = this._hash(key);
    if (!this.table[hash]) {
      this.table[hash] = {};
    }
    this.table[hash][key] = value;
  }

  get(key) {
    const hash = this._hash(key);
    if (this.table[hash] && this.table[hash][key]) {
      return this.table[hash][key];
    } else {
      return null;
    }
  }

  remove(key) {
    const hash = this._hash(key);
    if (this.table[hash] && this.table[hash][key]) {
      delete this.table[hash][key];
    }
  }

  _hash(key) {
    let sum = 0;
    for (let i = 0; i < key.length; i++) {
      sum += key.charCodeAt(i);
    }
    return sum % this.table.length;
  }
}
双向链表

双向链表是一种链式存储结构,它除了数据元素以外,还有两个指针 prev 和 next,分别指向前一个节点和后一个节点。

双向链表可以实现很多操作,比如在任意一个节点后插入、删除一个节点等。这些操作都可以在常数时间内完成,因为不需要像数组一样,遍历整个数组来查找元素。

下面是一个简单的双向链表的实现:

class Node {
  constructor(value) {
    this.value = value;
    this.prev = null;
    this.next = null;
  }
}

class DoublyLinkedList {
  constructor() {
    this.head = null;
    this.tail = null;
    this.size = 0;
  }

  append(value) {
    const node = new Node(value);
    if (!this.head) {
      this.head = node;
      this.tail = node;
    } else {
      node.prev = this.tail;
      this.tail.next = node;
      this.tail = node;
    }
    this.size++;
  }

  remove(node) {
    if (node.prev) {
      node.prev.next = node.next;
    } else {
      this.head = node.next;
    }
    if (node.next) {
      node.next.prev = node.prev;
    } else {
      this.tail = node.prev;
    }
    this.size--;
  }

  removeFirst() {
    if (!this.head) {
      return null;
    }
    const node = this.head;
    if (this.head === this.tail) {
      this.head = null;
      this.tail = null;
    } else {
      this.head = this.head.next;
      this.head.prev = null;
    }
    this.size--;
    return node;
  }
}
LRU 完整表格

有了哈希表和双向链表的基础,就可以实现 LRU 完整表格了。

首先,定义一个类 LRUCache,它有两个属性:capacity 和 map。capacity 表示缓存块的最大容量,map 是一个哈希表,用于将 key 映射到双向链表中的节点。

LRUCache 有三个方法:get、put 和 _refresh。

  • get 方法用于查询缓存,如果查询到了,返回对应的 value,然后将节点移到链表尾部,表示最近使用过;如果没有查询到,返回 -1。
  • put 方法用于插入缓存,如果 key 已经存在,更新对应的 value,并将节点移到链表尾部;如果 key 不存在,插入一个新节点,并将节点放到链表尾部。如果缓存已满,删除链表头部的节点,并从哈希表中删除对应的 key。
  • _refresh 方法用于将节点移到链表尾部,表示最近使用过。它接受一个节点作为参数,先将该节点从链表中删除,然后将该节点添加到链表尾部。

下面是 LRU 完整表格的实现:

class LRUCache {
  constructor(capacity) {
    this.capacity = capacity;
    this.map = new HashTable();
    this.list = new DoublyLinkedList();
  }

  get(key) {
    const node = this.map.get(key);
    if (node) {
      this.list.remove(node);
      this.list.append(node.value);
      return node.value.value;
    } else {
      return -1;
    }
  }

  put(key, value) {
    if (this.map.get(key)) {
      const node = this.map.get(key);
      node.value.value = value;
      this.list.remove(node);
      this.list.append(node.value);
    } else {
      if (this.list.size === this.capacity) {
        const node = this.list.removeFirst();
        this.map.remove(node.value.key);
      }
      const newNode = new Node({ key, value });
      this.list.append(newNode);
      this.map.put(key, newNode);
    }
  }

  _refresh(node) {
    this.list.remove(node);
    this.list.append(node.value);
  }
}
总结

LRU 完整表格是一种用于解决缓存问题的算法。它利用哈希表和双向链表实现,可以在常数时间内完成插入、查询、删除等操作。

熟悉哈希表和双向链表的实现过程,可以帮助我们更好地理解 LRU 的实现过程。这些数据结构是非常常用的,需要程序员熟练掌握。