演算法筆記-連結串列1
阿新 • • 發佈:2018-12-22
資料結構之連結串列
本文創作靈感來源於 極客時間 王爭老師的《資料結構與演算法之美》課程,通過課後反思以及借鑑各位學友的發言總結,現整理出自己的知識架構,以便日後溫故知新,查漏補缺。
是什麼
什麼是連結串列
- 資料結構上:連結串列也是一種線性表資料結構。
- 記憶體結構上:連結串列不需要連續的記憶體空間,通過指標將零散的記憶體塊串聯起來,構成連結串列的儲存空間。
- 連結串列中每一塊記憶體區域又叫節點,節點中儲存有基礎資料,以及指向下一個節點地址的後繼指標。
為什麼
為什麼有連結串列
- 對於插入和刪除操作,連結串列的時間複雜度都為 ,對於訪問某個節點,時間複雜度為 。
- 由於連結串列利用的是零散的記憶體空間,所以堆記憶體空間的利用率比陣列更高。
怎麼辦
怎麼學習連結串列
- 先認識常用的幾種連結串列:單鏈表,迴圈連結串列,雙向連結串列
- 其次學習基於連結串列實現的LRU快取淘汰演算法
單鏈表
- 每個節點包含一個數據域,儲存資料,一個指標域,指標域儲存下一節點的記憶體地址。
- 單鏈表中的兩個特殊節點:頭結點,記錄連結串列的基地址,有了它就能讓我們遍歷整個連結串列的節點。尾節點,連結串列的最後一個節點,它的指標域儲存的並不是下一個節點地址,而是儲存的 null 值。
- 單鏈表中的插入刪除操作時間複雜度就是 。如果刪除某個節點,只需將該節點的前一個節點的指標域指向該節點後續節點的記憶體地址,該節點的指標域指向 null 就完成了刪除。如果在某個位置新增節點,只需將新節點的指標域指向原位置上節點的後續節點的記憶體地址,原位置上節點的指標域指向新節點的記憶體地址就可以了。但是連結串列訪問第 N 個節點的時候就需要遍歷整個連結串列,時間複雜度就是 了。
迴圈連結串列
- 迴圈連結串列顧名思義就是首尾相接,迴圈往復的連結串列。在迴圈列表中,尾節點的指標域不在儲存 null 了,而是儲存頭結點的記憶體地址。
- 當要處理的資料具有環形結構的時候,最適合用迴圈連結串列來解決,比如約瑟夫問題。
雙向連結串列
- 在雙向連結串列中,每個節點包含一個前驅指標域,資料域,後驅指標域。前驅指標域儲存前驅節點的記憶體地址,後驅指標域儲存後驅節點的記憶體地址。
- 由於雙向連結串列任一節點查詢前驅節點和後驅節點的時間複雜度為 ,所以它在插入刪除操作上比單鏈表更加效率。
- 對比單鏈表,插入和刪除操作分為以下兩種情況:以刪除為例,給定數值,刪除值等於該數值的節點;給定節點記憶體地址,刪除連結串列中的該節點。刪除給定值的時候,兩種連結串列刪除操作時間複雜度為 ,但是找到該節點的時間複雜度都是 ,所以最終時間複雜度為 。如果是刪除給定地址的節點,單鏈表中,仍然需要遍歷連結串列,找到該節點的前一個節點,才能完成刪除,遍歷的時間複雜度為 ,最終的時間複雜度還是 。但是對於雙向連結串列來說,通過給定節點可以直接查到它的前一個節點,時間複雜度為 ,所以最終的時間複雜度為 。
- 在一個有序的單鏈表和雙向連結串列中,查詢到某一個值得節點,雙向連結串列的效率相比更高。因為我們可以記錄上一次查詢的節點 node ,下次查詢時,對比一下 node 的值,我們就知道該往連結串列的前面還是後面查詢,平均每次只會查詢一半的資料。
- 雙向連結串列犧牲了空間,換取了時間上的效率。在java語言中, LinkedHashMap 底層就是用的雙向連結串列實現的。
陣列與連結串列
- 陣列與連結串列同樣都是線性資料結構,但是儲存佔用的記憶體空間確實不一樣的。陣列佔據連續的記憶體空間,如果申請一個 10M 的陣列儲存空間,記憶體中的剩餘空間大於 10M ,但是沒有連續的 10M 記憶體空間,陣列申請依然會失敗。連結串列佔用的確是記憶體中零散的記憶體空間,記憶體利用率上更高。
- 陣列的儲存結構決定了陣列隨機訪問其中的元素時,時間複雜度為 ,但是插入刪除就是 ,而連結串列正好相反,隨機訪問元素的時間複雜度為 ,給定地址插入刪除的時間複雜度為 。另外,連結串列支援動態擴容,雖然一些陣列的容器也提供了動態擴容,但是陣列足夠大的時候,再次擴容效率是很低的。
- 對連結串列頻繁的刪除插入會導致頻繁的記憶體申請與釋放,容易造成記憶體碎片,對於java語言來說,更會導致頻繁地GC。
基於連結串列實現的LRU快取淘汰演算法
- 我們維護一個有序的連結串列,越靠近尾部的節點,說明是越早被訪問過的。當有新資料被訪問時,遍歷連結串列,找到值相同的節點,將其從連結串列中刪除,並在頭結點後面插入該值相同的節點。如果連結串列中沒有儲存該資料的節點:1,連結串列尚未存滿,則建立新的節點,儲存該資料,節點插入到連結串列的頭結點後面。2.連結串列已經存滿,則刪除連結串列的最後一個節點,建立新的節點,儲存該資料,節點插入到連結串列的頭結點後面。這就實現了一個LRU快取淘汰演算法。
- 常見的三種快取策略:先進先出策略 FIFO ,最少使用策略 LFU ,最近最少使用策略 LRU 。
總結
初入演算法複雜度分析,必是步履蹣跚,一路磕磕絆絆跌跌撞撞。看不懂別慌,也別忙著總結,先讀五遍文章先,無他,唯手熟爾~
與諸君共勉