1. 程式人生 > >如何實現LRU快取淘汰演算法

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

快取是一種提高資料讀取效能的技術,比如常見的cpu快取以及瀏覽器快取!但是快取的大小有限,當快取用滿的時候,哪些資料應該被清理出去,哪些資料應該被保留?

解決方案:FIFO--->先進先出      LFU---> 最少使用    LRU-->最近最少使用 

比方:買來很多技術書太佔用書房空間了,這時候會選擇扔掉一些書籍,但是採取啥樣的策率呢?一般就是上面三種策率!

問題:如何使用連結串列實現LRU快取淘汰策率呢?

1.五花八門的連結串列結構

1)陣列與連結串列底層儲存結構的對比

詳解:陣列需要一塊連續的記憶體空間來儲存,對記憶體的要求比較高。如果我們申請一個100M記憶體的大小的陣列,當記憶體中沒有連續足夠大的空間時,即便記憶體中沒有連續足夠大的儲存空間的時候,仍然會申請失敗!而連結串列恰恰相反,它並不許呀一塊連續的記憶體空間,它通過指標將一組零散的記憶體塊串聯起來使用,所以我們申請一個100MB大小的連結串列,根本不會有問題。

2)連結串列的分類

連結串列:通過指標將一組零散的記憶體快串聯在一起,我們把記憶體塊稱為連結串列的節點,為了將所有節點都串起來,每個連結串列的節點除了儲存資料之外,還需要記錄下一個結點的地址。我們把這個記錄下個結點地址的指標叫作後繼指標next

11)單鏈表

其中倆個節點比較特殊,分別是第一個節點和最後一個節點,我們把第一個結點叫作頭結點,把最後一個節點叫作尾節點,頭節點用來記錄連結串列的基地址,有了它,就可以遍歷整個連結串列。而尾節點特殊的地方是:指標不指向下一個結點,而是指向一個空地址null,表示這個是連結串列上最後一個結點。

連結串列與陣列相比,插入和刪除(僅僅對本身單獨的操作)效率更高,時間複雜度為O(1),陣列時間複雜度為O(n)

22)迴圈連結串列

定義:是一種特殊的單鏈表,實際上鍊表也很簡單,它和單鏈表唯一的區別就在尾結點,迴圈連結串列的尾結點指標是指向連結串列的頭結點

與單鏈表相比的優點:從鏈尾到鏈頭比較方便,當要處理的資料具有環形結構特點的時候,就特別適合採用迴圈連結串列。

33)雙向連結串列

單向連結串列只有一個方向,結點只有一個後繼指標next指向後面的結點。而雙向連結串列,顧名思義,它支援倆個方向,每個結點不止有一個後繼指標next指向後面的結點,還有一個前繼指標prev指向前面的結點。

雙向連結串列需要額外的倆個空間來儲存後繼結點和前驅結點的地址。所以,儲存同樣多的資料,雙向連結串列要比單鏈表佔用更多的儲存空間。雖然倆個指標比較浪費儲存空間,但可以支援雙向遍歷,這樣給雙向連結串列帶來靈活性!

雙向連結串列適合解決哪種問題呢

雙向連結串列可以支援O(1)時間複雜度的情況下,找到前驅結點,正是這樣的特點使雙向連結串列在某些情況下的插入刪除更加的高效。

問題:單鏈表的插入和刪除時間複雜度已經是O(1)呢?還怎麼進行高效呢?

在實際的開發中,從連結串列中刪除一個數據無外乎倆種情況
.刪除結點中 “值等於某個定值”的結點

.刪除給定指標指向的結點

對於第一種情況,不管是單鏈表還是雙鏈表,為了查詢某個值等於給定值的結點,都需要從頭開始一個一個一次遍歷對比,直到找到值等於給定值的結點,然後再通過指標操作將其刪除。儘管單純的刪除操作時間複雜度是O(1),但遍歷查詢的時間是主要的耗時點,對應的時間複雜度為O(n),所以刪除值等於給定值的時間複雜度為O(n)

對於第二種情況,我們已經找到了要刪除的結點,但是刪除某結點需要知道其前驅結點,單鏈表是不支援獲取前驅結點的,所以為了找到前驅結點,我們還是要從頭開始去尋找,直到p--next=q,這個時候說明p是q的前驅結點。但是對於雙向連結串列來說,因為雙向連結串列中的結點已經儲存了前驅結點的指標,不需要像單鏈表一樣去遍歷,所以倆者的複雜度分別為O(n)和O(1)

同樣的對於插入操作,如果在某個指定結點之前插入一個值,雙向連結串列對比單向連結串列也具有很大的優勢,時間複雜度分別為O(1)和O(n)

44)雙向迴圈連結串列

3)連結串列陣列大比拼

2.如何使用連結串列實現LRU快取淘汰策率呢?

思路:

我們去維護一個有序單向連結串列,越靠近連結串列尾部的結點是越早之前訪問的。當有一個新的資料被訪問的時候,我們從頭開始順序遍歷連結串列。

1)如果此資料之前已經被快取在連結串列中了,我們遍歷得到這個資料對應的結點,並將其從原來的位置刪除,然後再插入到連結串列的頭部。

2)如果此資料沒有被快取在連結串列中,又可以分為倆種情況:

11)如果此時快取未滿,則將此結點直接插入到連結串列的頭部

22)如果此時快取已滿,則連結串列尾結點刪除,將新的資料結點插入到連結串列的頭部。