鍊錶 如何實現LRU快取淘汰演算法

2022-05-21 04:21:29 字數 4230 閱讀 3465

一、什麼是鍊錶?

和陣列一樣,鍊錶也是一種線性表。

從記憶體結構來看,鍊錶的記憶體結構是不連續的記憶體空間,是將一組零散的記憶體塊串聯起來,從而進行資料儲存的資料結構。

二、為什麼使用鍊錶?即鍊錶的特點

三、常用鍊錶:單鏈表、迴圈鍊錶和雙向鍊錶

四、選擇陣列還是鍊錶?

五、應用

如何分別用鍊錶和陣列實現lru緩衝淘汰策略?

什麼是快取?

快取是一種提高資料讀取效能的技術,在硬體設計、軟體開發中都有著非廣泛的應用,比如常見的cpu快取、資料庫快取、瀏覽器快取等等。

為什麼使用快取?即快取的特點

快取的大小是有限的,當快取被用滿時,哪些資料應該被清理出去,哪些資料應該被保留?就需要用到快取淘汰策略。

什麼是快取淘汰策略?

指的是當快取被用滿時清理資料的優先順序。

有哪些快取淘汰策略?

常見的3種包括先進先出策略fifo(first in,first out)、最少使用策略lfu(least frequently used)、最近最少使用策略lru(least recently used)。

鍊錶實現lru快取淘汰策略

當訪問的資料沒有儲存在快取的鍊錶中時,直接將資料插入煉表表頭,時間複雜度為o(1);當訪問的資料存在於儲存的鍊錶中時,將該資料對應的節點,插入到煉表表頭,時間複雜度為o(n)。如果快取被佔滿,則從鍊錶尾部的資料開始清理,時間複雜度為o(1)。

陣列實現lru快取淘汰策略

方式一:首位置儲存最新訪問資料,末尾位置優先清理

當訪問的資料未存在於快取的陣列中時,直接將資料插入陣列第乙個元素位置,此時陣列所有元素需要向後移動1個位置,時間複雜度為o(n);當訪問的資料存在於快取的陣列中時,查詢到資料並將其插入陣列的第乙個位置,此時亦需移動陣列元素,時間複雜度為o(n)。快取用滿時,則清理掉末尾的資料,且剩餘陣列元素需整體後移一位,時間複雜度為o(n)。

方式二:首位置優先清理,末尾位置儲存最新訪問資料

當訪問的資料未存在於快取的陣列中時,直接將資料新增進陣列作為當前最後乙個元素時間複雜度為o(1);當訪問的資料存在於快取的陣列中時,查詢到資料並將其插入當前陣列最後乙個元素的位置,此時亦需移動陣列元素,時間複雜度為o(n)。快取用滿時,則清理掉陣列首位置的元素,且剩餘陣列元素需整體前移一位,時間複雜度為o(n)。(優化:清理的時候可以考慮一次性清理一定數量,從而降低清理次數,提高效能。)

六、設計思想

時空替換思想:「用空間換時間」 與 「用時間換空間」

當記憶體空間充足的時候,如果我們更加追求**的執行速度,我們就可以選擇空間複雜度相對較高,時間複雜度小相對較低的演算法和資料結構,快取就是空間換時間的例子。如果記憶體比較緊缺,比如**跑在手機或者微控制器上,這時,就要反過來用時間換空間的思路。

七、課後習題

如何判斷乙個字串是否是回文字串的問題,我想你應該聽過,我們今天的題目就是基於這個問題的改造版本。如果字串是通過單鏈表來儲存的,那該如何來判斷是乙個回文串呢?你有什麼好的解決思路呢?相應的時間空間複雜度又是多少呢?

方法一:半棧法

用快慢兩個指標遍歷,同時用棧copy慢指標指向的data。

完成後,慢指標指向中間節點,耗時為n/2.

最後用pop棧中的data和慢指標指向的data比較,耗時也是n/2.

所以時間複雜度為:o(n),空間複雜度因棧額外儲存了一半的data,故為o(n/2)

方法二:全棧法

全部遍歷,data壓棧,額外空間消耗n

再次全部遍歷取data,同時pop棧取data, 二者比較,時間消耗2n。

所以時間複雜度為o(3n),空間複雜度為o(n)。

該法演算法最簡單,但複雜度高。可以用棧儲存節點指標,而非data來改進。

方法三:硬幹法

乙個指標從頭取data,另乙個指標遍歷到底取data,比較二者,刪除尾部節點,重複1。

時間複雜度高達 o(n^2),空間複雜度卻最低o(1)。

/**

* definition for singly-linked list.

* public class listnode

* }*/class solution

listnode prev = null;

listnode slow = head;

listnode fast = head;

while (fast != null && fast.next != null)

if (fast != null)

while (slow != null)

slow = slow.next;

prev = prev.next;

}return true;

}}

一、理解指標或引用的含義

示例:p—>next = q; 表示p節點的後繼指標儲存了q節點的記憶體位址。

p—>next = p—>next—>next; 表示p節點的後繼指標儲存了p節點的下下個節點的記憶體位址。

二、警惕指標丟失和記憶體洩漏(單鏈表)

在插入和刪除結點時,要注意先持有後面的結點再操作,否者一旦後面結點的前繼指標被斷開,就無法再訪 問,導致記憶體洩漏。

插入節點

在節點a和節點b之間插入節點x,b是a的下一節點,,p指標指向節點a,則造成指標丟失和記憶體洩漏的**:p—>next = x;x—>next = p—>next; 顯然這會導致x節點的後繼指標指向自身。

正確的寫法是2句**交換順序,即:x—>next = p—>next; p—>next = x;

三、利用「哨兵」簡化實現難度

鍊錶的插入、刪除操作,需要對插入第乙個結點和刪除最後乙個節點做特殊處理。利用哨兵物件可以不用邊界判斷,鍊錶的哨兵物件是只存指標不存資料的頭結點。

什麼是「哨兵」?

鍊錶中的「哨兵」節點是解決邊界問題的,不參與業務邏輯。如果我們引入「哨兵」節點,則不管鍊錶是否為空,head指標都會指向這個「哨兵」節點。我們把這種有「哨兵」節點的鍊錶稱為帶頭鍊錶,相反,沒有「哨兵」節點的鍊錶就稱為不帶頭鍊錶。

未引入「哨兵」的情況

如果在p節點後插入乙個節點,只需2行**即可搞定:

new_node—>next = p—>next;

p—>next = new_node;

但,若向空煉表中插入乙個節點,則**如下:

if(head == null)
如果要刪除節點p的後繼節點,只需1行**即可搞定:

p—>next = p—>next—>next;

但,若是刪除鍊錶的最後乙個節點(鍊錶中只剩下這個節點),則**如下:

if(head—>next == null)
從上面的情況可以看出,針對鍊錶的插入、刪除操作,需要對插入第乙個節點和刪除最後乙個節點的情況進行特殊處理。這樣**就會顯得很繁瑣,所以引入「哨兵」節點來解決這個問題。

引入「哨兵」的情況

「哨兵」節點不儲存資料,無論鍊錶是否為空,head指標都會指向它,作為鍊錶的頭結點始終存在。這樣,插入第乙個節點和插入其他節點,刪除最後乙個節點和刪除其他節點都可以統一為相同的**實現邏輯了。

四、重點留意邊界條件處理

經常用來檢查鍊錶是否正確的邊界4個邊界條件:

如果鍊錶為空時

如果鍊錶只包含乙個節點時

如果鍊錶只包含兩個節點時

**邏輯在處理頭尾節點時

五、舉例畫圖,輔助思考

六、多寫多練,沒有捷徑

5個常見的鍊錶操作:

單鏈表反轉

鍊錶中環的檢測

兩個有序鍊錶合併

刪除鍊錶倒數第n個節點

求鍊錶的中間節點

練習題leetcode對應編號:206,141,21,19,876

哨兵**優勢: **2為什麼比**1效能更優? 為什麼**2少了乙個比較?

原因如下,首先要明確作者舉兩個**例子的目的是為了說明"哨兵"的優勢.

我們先分析沒有哨兵的**1,邏輯很簡單,在遍歷陣列的時候,挨個比較每乙個元素是否等於key,另外我們要還判斷迴圈條件i是否小於n,如果相等了,那麼就退出迴圈遍歷,所以針對每乙個元素判斷都進行了2次比較.

**2,一開始就把陣列中最後乙個元素修改成了目標key,while一次迴圈中,迴圈條件僅僅判斷當前陣列元素是否等於key,對於跳出迴圈的條件是省略的,為什麼呢?因為前面說了,陣列最後乙個元素改成了key,最後肯定會在陣列中找到key,也就是定會跳出. 於是最後我們只關注i是不是n-1就可以了,是n-1代表"原始整個陣列"元素中的確沒有key.

鍊錶 如何實現LRU快取淘汰演算法

乙個經典的鍊錶應用場景,就是lru快取淘汰演算法 快取是一種提高資料讀取效能的技術,比如cpu快取 資料庫快取 瀏覽器快取等等 快取的大小有限,當快取被用滿的時候哪些資料該被清理出去?哪些資料該被保留?需要快取淘汰策略來決定 先進先出策略fifo,最少使用策略lfu,最近最少使用策略lru 陣列需要...

鍊錶(上) 如何實現LRU快取淘汰演算法

鍊錶是一種最基礎的資料結構,學習鍊錶有什麼用?為了回答這個問題,先來討論乙個經典的鍊錶應用場景,那就是 lru 快取淘汰演算法。快取是一種提高資料讀取效能的技術,在硬體設計 軟體開發中都有著非常廣泛的應用,比如常見的 cpu 快取 資料庫快取 瀏覽器快取等等。快取的大小有限,當快取被用滿時,哪些資料...

06 鍊錶(上) 如何實現LRU快取淘汰演算法

我們先來討論乙個經典的鍊錶應用場景,那就是 lru 快取淘汰演算法。快取的大小有限,當快取被用滿時,哪些資料應該被清理出去,哪些資料應該被保留?這就需要快取淘汰策略來決定。常見的策略有三種 先進先出策略 fifo first in,first out 最少使用策略 lfu least frequen...