[一步步學數據結構與算法 19]-散列表(下)

帶着問題去學習:
1.爲什麼散列表和鏈表經常放在一起使用?
2.散列表和鏈表如何組合起來使用?

一、爲什麼散列表和鏈表經常放在一起使用?

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列表。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章