1105:散列表(下)
目錄
1.LRU(Least Recently Used)快取淘汰演算法
1.1.LRU快取淘汰演算法主要操作有哪些?主要包含3個操作:
帶著問題去學習:
為什麼散列表和連結串列經常放在一起使用?
散列表和連結串列如何組合起來使用?
一、為什麼散列表和連結串列經常放在一起使用?
1.散列表的優點:支援高效的資料插入、刪除和查詢操作
2.散列表的缺點:不支援快速順序遍歷散列表中的資料
3.如何按照順序快速遍歷散列表的資料?只能將資料轉移到陣列,然後排序,最後再遍歷資料。
4.我們知道散列表是動態的資料結構,需要頻繁的插入和刪除資料,那麼每次順序遍歷之前都需要先排序,這勢必會造成效率非常低下。
5.如何解決上面的問題呢?就是將散列表和連結串列(或跳錶)結合起來使用。
二、散列表和連結串列如何組合起來使用?
1.LRU(Least Recently Used)快取淘汰演算法
1.1.LRU快取淘汰演算法主要操作有哪些?主要包含3個操作:
①往快取中新增一個數據;
②從快取中刪除一個數據;
③在快取中查詢一個數據;
④總結:上面3個都涉及到查詢。
1.2.如何用連結串列實現LRU快取淘汰演算法?
①需要維護一個按照訪問時間從大到小的有序排列的連結串列結構。
②緩衝空間有限,當空間不足需要淘汰一個數據時直接刪除連結串列頭部的節點。
③當要快取某個資料時,先在連結串列中查詢這個資料。若未找到,則直接將資料放到連結串列的尾部。若找到,就把它移動到連結串列尾部。
④前面說了,LRU快取的3個主要操作都涉及到查詢,若單純由連結串列實現,查詢的時間複雜度很高為O(n)。若將連結串列和散列表結合使用,查詢的時間複雜度會降低到O(1)。
1.3.如何使用散列表和連結串列實現LRU快取淘汰演算法?
①使用雙向連結串列儲存資料,連結串列中每個節點儲存資料(data)、前驅指標(prev)、後繼指標(next)和hnext指標(解決雜湊衝突的連結串列指標)。
②散列表通過連結串列法解決雜湊衝突,所以每個節點都會在兩條鏈中。一條鏈是雙向連結串列,另一條鏈是散列表中的拉鍊。前驅和後繼指標是為了將節點串在雙向連結串列中,hnext指標是為了將節點串在散列表的拉鍊中。
③LRU快取淘汰演算法的3個主要操作如何做到時間複雜度為O(1)呢?
首先,我們明確一點就是連結串列本身插入和刪除一個節點的時間複雜度為O(1),因為只需更改幾個指標指向即可。
接著,來分析查詢操作的時間複雜度。當要查詢一個數據時,通過散列表可實現在O(1)時間複雜度找到該資料,再加上前面說的插入或刪除的時間複雜度是O(1),所以我們總操作的時間複雜度就是O(1)。
2.Redis有序集合
2.1.什麼是有序集合?
①在有序集合中,每個成員物件有2個重要的屬性,即key(鍵值)和score(分值)。
②不僅會通過score來查詢資料,還會通過key來查詢資料。
2.2.有序集合的操作有哪些?
舉個例子,比如使用者積分排行榜有這樣一個功能:可以通過使用者ID來查詢積分資訊,也可以通過積分割槽間來查詢使用者ID。這裡使用者ID就是key,積分就是score。所以,有序集合的操作如下:
①新增一個物件;
②根據鍵值刪除一個物件;
③根據鍵值查詢一個成員物件;
④根據分值區間查詢資料,比如查詢積分在[100.356]之間的成員物件;
⑤按照分值從小到大排序成員變數。
這時可以按照分值將成員物件組織成跳錶結構,按照鍵值構建一個散列表。那麼上面的所有操作都非常高效。
3.Java LinkedHashMap
和LRU快取淘汰策略實現一模一樣。支援按照插入順序遍歷資料,也支援按照訪問順序遍歷資料。
三、課後思考
1.上面所講的幾個散列表和連結串列組合的例子裡,我們都是使用雙向連結串列。如果把雙向連結串列改成單鏈表,還能否正常工作?為什麼呢?
2.假設獵聘網有10萬名獵頭,每個獵頭可以通過做任務(比如釋出職位)來積累積分,然後通過積分來下載簡歷。假設你是獵聘網的一名工程師,如何在記憶體中儲存這10萬個獵頭的ID和積分資訊,讓它能夠支援這樣幾個操作:
1)根據獵頭ID查收查詢、刪除、更新這個獵頭的積分資訊;
2)查詢積分在某個區間的獵頭ID列表;
3)查詢按照積分從小到大排名在第x位到第y位之間的獵頭ID列表。