オリジン
最近、学習アルゴリズムに関する小さなコースを買いました。
next.jsを使っていた頃、最適化のために訪問したページをキャッシュするためにLRUを使うという記事を読みました。
Redisも内部でLRUキャッシュ機構を使っていると聞いたことがあります。
その結果、学ぶべき情報を探したので、また忘れたときのためにここに記録しておきます。
LRUキャッシュ・メカニズムとは
LRU キャッシング・メカニズム、つまり最近使用されたデータの少ないキャッシング・ポリシーが使用されます。これは、あるデータが最近アクセスされていない場合、将来アクセスされる可能性も低いという原理に基づいています。つまり、限られたメモリ領域で他に利用できる領域がない場合、最も長い期間アクセスされていないデータは削除されるべきです。
下図をご覧ください:
3つの白いブロックは、先入れ先出しの法則に従う連鎖表と見なすことができ、この連鎖表に1、0、7を詰め込むと、7は最も長い未使用データとして排除されます。
0は一度しか使わないので、原則に従って下から頭へ移動させます。
leetcode146
データ構造の知識を使って、LRUキャッシング・メカニズムを設計し、実装してください。この機構は、次の操作をサポートする必要があります: データの取得とデータの書き込み。
データの取得 get(key) - キーの値がキャッシュに存在する場合はその値を取得し、存在しない場合は -1 を返します。 データの書き込み put(key, value) - キーのデータ値が既に存在する場合はその値を変更し、存在しない場合は "key/value" のセットを挿入します。キャッシュの容量が限界に達すると、新しいデータを書き込む際に、最も長い未使用のデータ値を削除して、新しいデータ値のためのスペースを確保する必要があります。
上級者向け。
両方の操作をO(1)の時間複雑度で実行できますか?
例
LRUCache cache = new LRUCache( 2 /* キャッシュサイズ*/ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // リターン1
cache.put(3, 3); // このアクションはキーワード2を無効にする
cache.get(2); //  -1 (見つからない)
cache.put(4, 4); // この操作はキーワード1を無効にする
cache.get(1); //  -1 (見つからない)
cache.get(3); // 3へ戻る
cache.get(4); // バック4
leetcode問題146の解答
トピックの要件は、O(1)の時間複雑さで両方の操作を完了することです。
MapとChained Listには2つのデータ構造があります。
Mapのキー値にアクセスする時間の複雑さはO(1)です。
連鎖テーブルへのノードの追加と削除の時間複雑度はO(1)
次の図は、マップと連鎖テーブルの関係を明確に表しています:
Java
class LRUCache {
 private Map<Integer, LRUNode> cache = new HashMap<>();
 private int count;
 private int capacity;
 private LRUNode head, tail;
 public LRUCache(int capacity) {
 this.count = 0;
 this.capacity = capacity;
 this.head = new LRUNode();
 this.tail = new LRUNode();
 head.next = tail;
 tail.prev = head;
 }
 
 public int get(int key) {
 LRUNode node = cache.get(key);
 if (node != null) {
 	// ノードが存在する、最近使用された、ノードをトップに置く
 this.moveToHead(node);
 return node.value;
 }
 return -1;
 }
 
 public void put(int key, int value) {
 LRUNode node = cache.get(key);
 if (node != null) {
 	// ノードが存在する、最近使用された、ノードをトップに置く
 node.value = value;
 this.moveToHead(node);
 } else {
 	// ノードが存在しないので、新しいノードをトップに追加する
 LRUNode newNode = new LRUNode(key, value);
 this.cache.put(key, newNode);
 this.addNode(newNode);
 ++this.count;
 
 if (this.count > this.capacity) {
 	// 容量を超えたら一番下のノードを削除する
 LRUNode tail = this.removeTail();
 this.cache.remove(tail.key);
 --this.count;
 }
 }
 }
 private void moveToHead(LRUNode node) {
 this.removeNode(node);
 this.addNode(node);
 }
 private void addNode(LRUNode node) {
 node.prev = head;
 node.next = head.next;
 head.next.prev = node;
 head.next = node;
 }
 private void removeNode(LRUNode node) {
 node.prev.next = node.next;
 node.next.prev = node.prev;
 }
 private LRUNode removeTail() {
 LRUNode res = tail.prev;
 removeNode(res);
 return res;
 }
 private class LRUNode {
 int key;
 int value;
 LRUNode prev;
 LRUNode next;
 LRUNode() {}
 LRUNode(int key, int value) {
 this.key = key;
 this.value = value;
 }
 }
}
JavaScript
/**
 * @param {any} value
 */
var isNil = function(value) {
 return value === null || value === undefined;
}
/**
 * @param {number} key
 * @param {number} value 
 */
var LRUNode = function(key, value) {
 this.key = key;
 this.value = value;
 this.prev = null;
 this.next = null;
}
/**
 * @param {number} capacity
 */
var LRUCache = function(capacity) {
 this.size = 0;
 this.capacity = capacity;
 this.cache = new Map();
 this.head = new LRUNode(null, null);
 this.tail = new LRUNode(null, null);
 this.head.next = this.tail;
 this.tail.prev = this.head;
};
/** 
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function(key) {
 var node = this.cache.get(key);
 if (!isNil(node)) {
 this.moveToHead(node);
 return node.value;
 }
 return -1;
};
/** 
 * @param {number} node
 */
LRUCache.prototype.moveToHead = function(node) {
 this.removeNode(node);
 this.addNode(node);
};
/** 
 * @param {number} node
 */
LRUCache.prototype.removeNode = function(node) {
 var prevNode = node.prev;
 var nextNode = node.next;
 if (!isNil(nextNode))
 prevNode.next = node.next;
 if (!isNil(nextNode))
 nextNode.prev = node.prev;
};
/** 
 * @param {number} node
 */
LRUCache.prototype.addNode = function(node) {
 node.prev = this.head;
 node.next = this.head.next;
 this.head.next.prev = node;
 this.head.next = node;
};
/** 
 * @param {number} node
 */
LRUCache.prototype.removeTail = function() {
 var node = this.tail.prev;
 this.removeNode(node);
 return node;
};
/** 
 * @param {number} key 
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function(key, value) {
 var node = this.cache.get(key);
 if (!isNil(node)) {
 node.value = value;
 this.moveToHead(node);
 } else {
 var newNode = new LRUNode(key, value);
 this.cache.set(key, newNode);
 this.addNode(newNode);
 this.size ++;
 if (this.size > this.capacity) {
 var tailNode = this.removeTail();
 this.cache.delete(tailNode.key);
 this.size --;
 }
 }
};





