如何使用鍊表實現 LRU 算法

2021-01-18 BitLegend

什麼是 LRU 算法

LRU 是一種緩存淘汰策略。計算機的緩存容量有限,如果緩存滿了就要刪除一些內容,給新的內容騰位置。但是要刪除哪些內容呢?我們肯定希望刪掉那些沒有用的緩存,而把有用的數據繼續留在緩存中,方便之後繼續使用。LRU 的全稱是 Least Recently Used,也就是說我們認為最近使用過的數據應該是有用的,很久都沒用過的數據應該是無用的,緩存滿了就優先刪除那些很久沒有用過的數據。

LRU 算法的特點

首先是緩存的大小是有限的。每次從緩存當中獲取數據的時候,如果獲取成功會將數據移動到最頭部。同時新添加的元素也是在頭部。當緩存大小達到上限之後,添加元素時會刪除最久未使用的元素,也就是鍊表的最後一個元素,然後將新的元素插入在鍊表頭。

LRU 的應用場景

LRU 算法可以用來管理我們的緩存數據。控制我們的緩存大小,用較少的緩存空間達到更高的緩存數據。舉例來說我們可以將一些不容易發生變化的數據且頭部效應表中的數據加入到緩存當中。

編碼實現

結構定義

#include <stdio.h>#include <stdlib.h>#include <string.h>// 默認容量#define N 10// 表示這個鍊表的長度信息int len = 0;//當前鍊表的元素個數信息int count = 0;typedefstruct Node{ /* data */char *key; char *value; struct Node *next; struct Node *prev;} Node;// 鍊表的頭節點和尾節點struct Node *head;struct Node *tail;// 函數預聲明// 創建節點Node *createNode(char *key, char *value);// 插入節點到頭部void insertHead(Node *node);// 移除尾部節點void removeTail();// 添加節點操作void add(char *key, char *value);// 刪除鍊表中的一個節點Node *deleteNode(Node *node);// 獲取指定key的值char *get(char *key);// 銷毀數據void destory();插入操作

// 獲取一個元素void add(char *key, char *value){ Node *node = createNode(key, value); //第一個元素if (head == NULL) { insertHead(node); return; } //判斷整個鍊表中是否存在整個元素 Node *now = deleteNode(node); // 如果找到了元素 將元素移動至末尾 結束方法if (now != NULL) { // 設置新的值 now->value = value; insertHead(now); return; } // 此時鍊表中是不存在這個元素// 判斷鍊表的長度if (count >= len) { removeTail(); } // 將新元素添加至末尾 insertHead(node); return;}獲取操作

char *get(char *key){ if (key == NULL) { return NULL; } // 尋找元素 Node *node = createNode(key, NULL); Node *now = deleteNode(node); // 釋放空間free(node); // 元素存在if (now != NULL) { //將元素調整到末尾 insertHead(now); return now->value; } return NULL;}基本操作函數

// 創建一個節點Node *createNode(char *key, char *value){ Node *node = (Node *)malloc(sizeof(Node)); node->key = key; node->value = value; node->prev = node->next = NULL; return node;}// 將節點插入到頭節點部分void insertHead(Node *node){ // 元素為空時if (head == NULL) { tail = head = node; count++; return; } node->next = head; head->prev = node; // 移動鍊表的末尾指針 head = node; // 計數 count++;}// 移除void removeTail(){ //移除最久未使用的那個元素 Node *now = tail; if (now != NULL) { // 獲取前一個節點 Node *p = now->prev; if (p != NULL) { // 斷開當前節點 同時移動尾節點 p->next = NULL; tail = p; } else { head = tail = NULL; } // 當前節點置空 now->prev = now->next = NULL; // 元素減少 count--; // 釋放空間free(now); }}// 鍊表中刪除一個節點 刪除成功返回被刪除節點Node *deleteNode(Node *node){ Node *now = head; while (now != NULL) { // 存在節點if (strcmp(now->key, node->key) == 0) { // 獲取前後節點 Node *p = now->prev; Node *n = now->next; // 更新指向if (n != NULL) { n->prev = p; } else { tail = p; } if (p != NULL) { p->next = n; } else { head = n; } //當前節點置空 now->prev = NULL; now->next = NULL; count--; break; } now = now->next; } return now;}// 銷毀數據void destory(){ Node *node = head; while (node != NULL) { Node *n = node; free(n); node = node->next; } len = 0; count = 0; head = tail = NULL;}// 從頭節點開始列印整個鍊表void printLink(){ Node *now = head; while (now != NULL) { printf("[key=%s,value=%s]", now->key, now->value); now = now->next; } printf("\n");}最後的測試函數

int main(){ init(5); add("1", "1"); add("2", "2"); printLink(); char *res = get("1"); printLink(); printf("value=%s\n", res); add("3", "3"); add("4", "4"); add("5", "5"); add("6", "6"); printLink(); res = get("1"); printLink(); destory(); return0;}// 輸出結果:/*[key=2,value=2][key=1,value=1][key=1,value=1][key=2,value=2]value=1[key=6,value=6][key=5,value=5][key=4,value=4][key=3,value=3][key=1,value=1][key=1,value=1][key=6,value=6][key=5,value=5][key=4,value=4][key=3,value=3]*/以上就是一個鍊表實現 LRU 算法的大體代碼。已將代碼上傳至https://gitlab.com/BitLegend/c-data-structure.git

感謝你能看到這裡,歡迎關注我的公眾號:BitLegend,我們下期見!

相關焦點

  • 怎麼實現的延時隊列?以及訂閱模式、LRU
    另外一點,就是Redis實現的消息隊列,沒有ACK機制,所以想要實現消息的可靠性,還要自己實現當消息處理失敗後,能繼續拋回隊列。延時隊列用Redis實現延時隊列,其實就是使用zset來實現,將消息序列化成一個字符串(可以是json格式),作為為value,消息的到期處理時間做為score,然後用多線程去輪詢zset來獲取到期消息進行處理。
  • 了解LRU緩存的實現方法,這就夠了
    在之前的文章,我們介紹過常用緩存淘汰算法(文末有連結)。常見的緩存淘汰算法有先進先出淘汰算法(FIFO),最近最少使用淘汰算法(LSU),最近最久未使用算法(LRU),MRU(最近最常使用算法)。今天我們來討論其中的一種,也是最常用的LRU算法,看看它是如何實現的。
  • 地鐵五分鐘理解LRU算法
    LRU(Least recently used,最近最少使用)算法作為內存管理的一種有效算法,其含義是在內存有限的情況下,當內存容量不足時,為了保證程序的運行,這時就不得不淘汰內存中的一些對象,釋放這些對象佔用的空間,那麼選擇淘汰哪些對象呢?
  • 一文學會鍊表解題 - CSDN
    所以之後的習題講解中我們使用的鍊表都是使用定義了哨兵結點的形式。做了這麼多前期的準備工作,終於要開始我們的正餐了:鍊表解題常用套路--翻轉!(對照著代碼看,是不是清晰易懂^_^)非遞歸翻轉鍊表(迭代解法)我們知道遞歸比較容易造成棧溢出,所以如果有其他時間/空間複雜度相近或更好的算法,應該優先選擇非遞歸的解法,那我們看看如何用迭代來翻轉鍊表,主要思路如下步驟 1:定義兩個節點:pre, cur ,其中 cur 是 pre 的後繼結點,如果是首次定義, 需要把 pre 指向 cur 的指針去掉,否則由於之後鍊表翻轉
  • 回文鍊表 | Python
    重排鍊表 的思想有點類似,以下是前陣子寫的關於 143 題的解題思路,有興趣的話可以進行閱讀,多多支持。143. 重排鍊表 | 線性表、切分鍊表(迭代+雙指針)線性表 + 雙指針一般情況下,我們要求數組是否是回文,可以使用雙指針的話方法。
  • 鍊表(單鍊表)的基本操作及C語言實現
    線性表的鏈式存儲結構生成的表,稱作「鍊表」。鍊表中數據元素的構成每個元素本身由兩部分組成:本身的信息,稱為「數據域」;指向直接後繼的指針,稱為「指針域」。這兩部分信息組成數據元素的存儲結構,稱之為「結點」。n個節點通過指針域相互連結,組成一個鍊表。由於每個節點中只包含一個指針域,生成的鍊表又被稱為 線性鍊表 或 單鍊表。
  • 常考算法面試題系列:鍊表的操作
    反轉鍊表使用迭代:在遍歷列表時,將當前節點的 next 指針改為指向前一個元素。由於節點沒有引用其上一個節點,因此必須事先存儲其前一個元素。在更改引用之前,還需要另一個指針來存儲下一個節點。不要忘記在最後返回新的頭引用。
  • 歷史圖上的PageRank算法設計與實現
    在Wekipedia提供的網頁互相連接的Hyperlink networks數據集上將其性能與在鍊表上實現PageRank算法作比較,結果顯示其性能大大優於使用鍊表結構,並且隨著數據規模和目標時間規模的增大,其優勢將會越來越明顯。
  • 阿里三面慘遭被虐,關於數據結構與算法竟然一竅不通!
    以Java為描述語言,介紹計算機編程中使用的數據結構和算法,覆蓋相應競爭性考試的主題,目的不是提供關於數據結構和算法的定理及證明,而是強調問題及其分析,講解必備知識和解題技巧。書中匯集知名IT企業經典的編程面試題目並給出解題思路,為學生應試和軟體開發人員面試提供有益指導。本書特點:所有代碼用Java實現。數據結構難點啟發思考。為每個問題列舉可能的解決辦法。
  • leetcode鍊表之回文鍊表
    序本文主要記錄一下leetcode鍊表之回文鍊表題目
  • 網際網路公司最常見的面試算法題大集合
    該項目目前分為四個部分:第一個部分是 leetcode 經典題目的解析,包括思路,關鍵點和具體的代碼實現第二部分是對於數據結構與算法的總結第三部分是 anki 卡片, 將 leetcode 題目按照一定的方式記錄在 anki 中,方便大家記憶第四部分是計劃, 這裡會記錄將來要加入到以上三個部分內容
  • 2021屆秋招大廠高頻算法題匯總
    鍊表力扣109:將有序鍊表轉化為二叉搜素樹力扣141:環形鍊表判斷是否有環力扣142:環形鍊表檢測入口位置力扣143:重拍鍊表力扣160:相交鍊表力扣206:反轉鍊表力扣21:合併兩個有序鍊表力扣23:合併K和有序鍊表力扣234:迴文聯表力扣25:K個一組反轉鍊表力扣328:奇偶鍊表
  • 機器學習時代的哈希算法,將如何更高效地索引數據
    哈希算法一直是索引中最為經典的方法,它們能高效地儲存與檢索數據。但在去年 12 月,Jeff Dean 與 MIT 等研究者將索引視為模型,探索了深度學習模型學習的索引優於傳統索引結構的條件。本文首先將介紹什麼是索引以及哈希算法,並描述在機器學習與深度學習時代中,如何將索引視為模型學習比哈希算法更高效的表徵。
  • 《算法》筆記 11 - 最小生成樹
    貪心算法切分定理是所有解決最小生成樹問題算法的基礎,這些算法都是一種貪心算法的特殊情況,貪心算法是一類在每一步選擇中都採取在當前狀態下最好或最優的選擇,從而希望導致結果是最好或最優的算法。解決最小生成樹問題時,會使用切分定理找到最小生成樹的一條邊,不斷重複直到找到最小生成樹的所有邊。
  • 每天一道leetcode234-回文鍊表
    ://leetcode-cn.com/problems/palindrome-linked-list/英文鍊表:https://leetcode.com/problems/palindrome-linked-list/難度:easy分類:鍊表題目詳述 請判斷一個鍊表是否為回文鍊表。
  • 2018國家電網考試備考計算機之數據結構與算法(6)
    2018國家電網考試備考計算機之數據結構與算法(6)由北京事業單位考試網提供:更多關於2018國家電網考試,計算機數據結構與算法,事業單位考試網的內容請關注北京事業單位考試網!或關注北京華圖微信公眾號(bjhuatu),事業單位培訓諮詢電話:400-010-1568。
  • 原型鏈就是個鍊表,有啥好研究的,隔三差五來一下,走馬燈麼?
    】經微信公眾號授權轉載,如需轉載與原文作者聯繫自從重新拿起筆混跡掘金, 我幾乎每天都會打開掘金, 有時候也真的是審美疲勞, 雖然我大部分時間都是在看標題, 但是每隔幾天都有一個差不多的標題在你眼前飄過, 時間久了也有點麻木昨天在天天拉的群裡, 看一幫小朋友討論this的問題, 有種回到了七八年前剛學前端的時候, 我在前幾篇文章中關於數據結構和算法對前端到底有沒有用
  • Redis 設計與實現 4:字典
    字典的結構哈希表哈希表的實現代碼在:dict.h/dictht ,Redis 的字典用哈希表的方式實現。used 屬性用來記錄已經使用的節點數,size - use 就是未使用的節點啦。// 使用哈希字典裡面的哈希算法,算出哈希值hash = dict->type->hashFunction(key)// 使用 sizemask 和 哈希值算出索引值idx = hash & d->ht[table].sizemask;// 通過索引值,定位哈希節點he = d->ht[table].table[idx];哈希衝突哈希衝突指的是多個不同的
  • 如何實現算法中的公平性
    很明顯,預測器並未使用種族作為特徵變量,但是基於美國歷史上的不公,以及人口統計、社會、執法統計數據等因素看,「暴力歷史」、「職業教育」等變量的數據分布在不同種族間存在著顯著差異。而執法統計數據也同樣倍受爭議。警察巡邏的街區通常也是使用算法確定的,而算法使用了數據分布上的差異,引入了種族間的差異,進而在某種程度上導致結果偏向或是不利於某個種族。
  • Python機器學習5:使用scikit-learn實現三種集成學習Bagging算法
    如果你在解決一個問題時實現了多種模型,但是每個模型的效果都差不多,但是你想要進一步提升最後預測效果,那麼本文將強烈建議你使用集成學習算法來實現!集成學習算法指的是將已有的多種模型綜合起來,實現最終分類或者回歸。一般而言,集成學習可以提高原有算法模型的準確性。