學習鍊錶有什麼用?引出用鍊錶實現
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資料結構與演算法 四 鍊錶
鍊錶是一種物理儲存單元 上非連續 非順序的儲存結構,資料元素的邏輯順序是通過鍊錶中的指標 鏈結次序實現的。鍊錶由一系列結點 鍊錶中每乙個元素稱為結點 組成,結點可以在執行時動態生成。每個結點包括兩個部分 乙個是儲存資料元素的資料域,另乙個是儲存下乙個結點位址的指標 域。相比於線性表 順序結構,操作複...