資料結構與演算法 鍊錶(四)

2021-09-12 22:36:07 字數 4647 閱讀 2034

學習鍊錶有什麼用?引出用鍊錶實現

lru快取淘汰演算法

鍊錶:通過指標將一組零散的記憶體塊串聯在一起,其中我們把記憶體塊叫做鍊錶的節點

快取淘汰策略:

fifo

先進先出策略,

lfu(

least frequently used

)最少使用策略,

lru最近最少使用策略

(least recently used)

lru:活在當下。比如在公司中,乙個新員工做出新業績,馬上會得到重用。

lfu

1、刪除節點中  「值等於給定值」的節點:單,雙鏈表都需要 從頭遍歷,找到值等於給定值的節點,然後刪除

。主要耗時在查詢,所以時間複雜度位

o(n)

2、刪除給定指標指向的節點:  我們已經知道要刪除的節點

p,單鏈表需要從頭遍歷找到其前驅節點

q->next=q

,在刪除節點時間複雜度為

o(n); 雙向鍊錶就可以直接獲取其前驅節點,在刪除,時間複雜度為o(

1)雙向鍊錶查詢速度也優於單向鍊錶,有序鍊錶中,雙向鍊錶可以前後遍歷,查詢資料,只需要查詢一半資料即可

優點:簡單易用,在實現上使用的是連續的記憶體空間,可以借助

cpu的快取機制,預讀陣列中的資料,所以訪問效率高, 缺點

:陣列一經宣告就需要佔據大量連續記憶體空間,如果宣告陣列過大,系統可能沒有連續記憶體空間分配 鍊錶

: 缺點:在記憶體中並不是連續儲存,所以對

cpu快取不友好,沒辦法預讀

總結:如果對記憶體 的使用非常苛刻,建議使用陣列,鍊錶每個節點都需要耗費額外空間儲存下乙個節點的指標,記憶體消耗翻倍,頻繁刪除插入容易造成記憶體碎片

思想:

對於 執行較慢的程式可以通過消耗更多的記憶體(空間換時間)來進行優化

而消耗過多的記憶體 程式,可以消耗更多的時間(時間換空間)來降低記憶體的消耗

我們維護乙個有序單鏈表,越靠近底部的節點是越早之前訪問的,當有乙個新資料被訪問時,我們從煉表頭開始順序遍歷鍊錶 1.

如果該資料之前已經在鍊錶中,遍歷得到該資料,刪除其所在位置,然後插入鍊錶頭部 2.

如果該陣列沒有在快取鍊錶中, 1

)快取未滿,直接插入鍊錶頭部 2

)快取已滿,刪除鍊錶尾部資料,把該資料插入頭部

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

當訪問的資料未存在於快取的陣列中時,直接將資料插入陣列第乙個元素位置,此時陣列所有元素需要向後移動

1個位置,時間

複雜度為o(n);當訪問的資料存在於快取的陣列中時,查詢到資料並將其插入陣列的第乙個位置,此時亦需移動陣列元素,時間

複雜度為o(n)。快取用滿時,則清理掉末尾的資料,時間複雜度為

o(1)

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

當訪問的資料未存在於快取的陣列中時,直接將資料新增進陣列作為當前最有乙個元素時間複雜度為o(1);當訪問的資料存在於

快取的陣列中時,查詢到資料並將其插入當前陣列最後乙個元素的位置,此時亦需移動陣列元素,時間複雜度為o(n)。快取用滿

時,則清理掉陣列首位置的元素,且剩餘陣列元素需整體前移一位,時間複雜度為

o(n)。(優化:清理的時候可以考慮一次性清

理一定數量,從而降低清理次數,提高效能。)

技巧一:理解指標或引用的含義

指標中儲存了這個變數的記憶體位址,指向這個變數,通過指標可以找到這個變數

示例:p—>next = q; 表示p

節點的後繼指標儲存了

q節點的記憶體位址。

p—>next = p—>next—>next; 表示p

節點的後繼指標儲存了

p節點的下下個節點的記憶體位址。

技巧二:警惕指標丟失和記憶體洩漏

1、插入節點

在節點a

和節點b

之間插入節點x,

b是a的下一節點,,

p指標指向節點

a,則造成指標丟失和記憶體洩漏的**:p—>next =

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

顯然這會導致x節點的後繼指標指向自身。

正確的寫法是

2句**交換順序,即:x—>next = p—>next; p—>next = x;

2、刪除節點

在節點a

和節點b

之間刪除節點b,

b是a的下一節點,

p指標指向節點a:

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

技巧三:利用哨兵簡化實現難度

「哨兵」節點不儲存資料,無論鍊錶是否為空,

head

指標都會指向它,作為鍊錶的頭結點始終存在。這樣,插入第乙個節點和插入其他節點,刪除最後乙個節點和刪除其他節點都可以統一為相同的**實現邏輯

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

經常用來檢查鍊錶是否正確的邊界

4個邊界條件: 1、

如果鍊錶為空時,**是否能正常工作? 2、

如果鍊錶只包含乙個節點時,**是否能正常工作? 3、

如果鍊錶只包含兩個節點時,**是否能正常工作? 4、

**邏輯在處理頭尾節點時是否能正常工作?

五、舉例畫圖,輔助思考

六、多寫多練,沒有捷徑

5個常見的鍊錶操作:

1、

單鏈表反轉

// 1) 單鏈表反轉

public static node reverse(node list)

current.next = prev;

prev = current;

current = next;

} return head;

}

2、

鍊錶中環的檢測

// 2) 鍊錶中環的檢測

public static boolean checkcircle(node list)

node slow = list;

node fast = list.next;

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

} return false;

}

3、

兩個有序鍊錶合併

/**

* 3) 兩個有序的鍊錶合併

* * @param la 鍊錶a

* @param lb 鍊錶b

* @return

*/public static node mergesortedlists(node la, node lb) else

node r = head;

while (p != null && q != null) else

r = r.next;

} // 判斷 p、q 兩個鍊錶哪乙個先遍歷結束,最後將剩餘的鍊錶拼接到合成鍊錶的最後

if (p != null) else

return head;

}

4、

刪除鍊錶倒數第

n個節點

//	4) 刪除鍊錶倒數第n個結點 

public static node deletelastkth(node list, int k)

//如果單鏈表的長度小於 k ,就返回 list 單鏈表

if (fast == null) return list;

//前邊找到第 k 個結點之後,讓 slow 指向第乙個結點

node slow = list;

node prev = null;

//判斷fast指標也就是最前邊的指標下乙個節點是否為 null(如果為null相當於到尾部了)

while (fast.next != null)

//這個判斷是,如果單鏈表的長度正好等於 k ,刪除倒數第k個結點也就是刪除頭結點。

if (prev == null) else

//返回已經刪除結點的鍊錶

return list;

}

5、求鍊錶的中間節點

// 求鍊錶中間節點

public static node findmidde(node list)

node slow = list;

node fast = list.next;

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

return slow;

}

Java資料結構與演算法 四 鍊錶

鍊錶是一種物理儲存單元 上非連續 非順序的儲存結構,資料元素的邏輯順序是通過鍊錶中的指標 鏈結次序實現的。鍊錶由一系列結點 鍊錶中每乙個元素稱為結點 組成,結點可以在執行時動態生成。每個結點包括兩個部分 乙個是儲存資料元素的資料域,另乙個是儲存下乙個結點位址的指標 域。相比於線性表 順序結構,操作複...

Java資料結構與演算法 四 鍊錶

鍊錶是一種物理儲存單元 上非連續 非順序的儲存結構,資料元素的邏輯順序是通過鍊錶中的指標 鏈結次序實現的。鍊錶由一系列結點 鍊錶中每乙個元素稱為結點 組成,結點可以在執行時動態生成。每個結點包括兩個部分 乙個是儲存資料元素的資料域,另乙個是儲存下乙個結點位址的指標 域。相比於線性表 順序結構,操作複...

Java資料結構與演算法 四 鍊錶

鍊錶是一種物理儲存單元 上非連續 非順序的儲存結構,資料元素的邏輯順序是通過鍊錶中的指標 鏈結次序實現的。鍊錶由一系列結點 鍊錶中每乙個元素稱為結點 組成,結點可以在執行時動態生成。每個結點包括兩個部分 乙個是儲存資料元素的資料域,另乙個是儲存下乙個結點位址的指標 域。相比於線性表 順序結構,操作複...