【17】散列表(下):爲什麼散列表和鏈表經常會一起使用?

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

  1. 散列表的優點:支持高效的數據插入、刪除和查找操作
  2. 散列表的缺點:不支持快速順序遍歷散列表中的數據
  3. 如何按照順序快速遍歷散列表的數據?只能將數據轉移到數組,然後排序,最後再遍歷數據。
  4. 我們知道散列表是動態的數據結構,需要頻繁的插入和刪除數據,那麼每次順序遍歷之前都需要先排序,這勢必會造成效率非常低下。
  5. 如何解決上面的問題呢?就是將散列表和鏈表(或跳錶)結合起來使用。

2. 散列表和鏈表如何組合起來使用的案例

  1. LRU(Least Recently Used)緩存淘汰算法
    LRU緩存淘汰算法主要操作有哪些?主要包含3個操作:
    1)往緩存中添加一個數據;
    2)從緩存中刪除一個數據;
    3)在緩存中查找一個數據;
    總結:上面3個都涉及到查找。
    如何用鏈表實現LRU緩存淘汰算法?
    1)需要維護一個按照訪問時間從大到小的有序排列的鏈表結構。
    2)緩衝空間有限,當空間不足需要淘汰一個數據時直接刪除鏈表頭部的節點。
    3)當要緩存某個數據時,先在鏈表中查找這個數據。若未找到,則直接將數據放到鏈表的尾部。若找到,就把它移動到鏈表尾部。
    4)前面說了,LRU緩存的3個主要操作都涉及到查找,若單純由鏈表實現,查找的時間複雜度很高爲O(n)。若將鏈表和散列表結合使用,查找的時間複雜度會降低到O(1)。
    如何使用散列表和鏈表實現LRU緩存淘汰算法?
    1)使用雙向鏈表存儲數據,鏈表中每個節點存儲數據(data)、前驅指針(prev)、後繼指針(next)和hnext指針(解決散列衝突的鏈表指針)。
    2)散列表通過鏈表法解決散列衝突,所以每個節點都會在兩條鏈中。一條鏈是雙向鏈表,另一條鏈是散列表中的拉鍊。前驅和後繼指針是爲了將節點串在雙向鏈表中,hnext指針是爲了將節點串在散列表的拉鍊中。
    3)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。所以,有序集合的操作如下:
    1)添加一個對象;
    2)根據鍵值刪除一個對象;
    3)根據鍵值查找一個成員對象;
    4)根據分值區間查找數據,比如查找積分在[100.356]之間的成員對象;
    5)按照分值從小到大排序成員變量。
    這時可以按照分值將成員對象組織成跳錶結構,按照鍵值構建一個散列表。那麼上面的所有操作都非常高效。
  3. Java LinkedHashMap
    和LRU緩存淘汰策略實現一模一樣。支持按照插入順序遍歷數據,也支持按照訪問順序遍歷數據。

3. 課後思考

  1. 上面所講的幾個散列表和鏈表組合的例子裏,我們都是使用雙向鏈表。如果把雙向鏈表改成單鏈表,還能否正常工作?爲什麼呢?
    答:在刪除一個元素時,雖然能 O(1) 的找到目標結點,但是要刪除該結點需要拿到前一個結點的指針,遍歷到前一個結點複雜度會變爲 O(N),所以用雙鏈表實現比較合適。
    (但其實硬要操作的話,單鏈表也是可以實現 O(1) 時間複雜度刪除結點的)。

  2. 假設獵聘網有10萬名獵頭,每個獵頭可以通過做任務(比如發佈職位)來積累積分,然後通過積分來下載簡歷。假設你是獵聘網的一名工程師,如何在內存中存儲這10萬個獵頭的ID和積分信息,讓它能夠支持這樣幾個操作:
    1)根據獵頭ID查收查找、刪除、更新這個獵頭的積分信息;
    2)查找積分在某個區間的獵頭ID列表;
    3)查找按照積分從小到大排名在第x位到第y位之間的獵頭ID列表。
    答:以積分排序構建一個跳錶,再以獵頭 ID 構建一個散列表。
    1)ID 在散列表中所以可以 O(1) 查找到這個獵頭;
    2)積分以跳錶存儲,跳錶支持區間查詢;
    3)這點根據目前學習的知識暫時無法實現,老師文中也提到了。

4. 小結

數組佔據隨機訪問的優勢,卻有需要連續內存的缺點。
鏈表具有可不連續存儲的優勢,但訪問查找是線性的。
散列表和鏈表、跳錶的混合使用,是爲了結合數組和鏈表的優勢,規避它們的不足。
我們可以得出數據結構和算法的重要性排行榜:連續空間 > 時間 > 碎片空間。

5. 參考資料

  1. 王爭老師在極客時間的專欄《數據結構與算法之美》
  2. 專欄下的所有評論

6. 聲明

本文章是學習王爭老師在極客時間專欄——《數據結構與算法之美》的學習總結,文章很多內容直接引用了專欄下的回覆,推薦大家購買王爭老師的專欄進行更加詳細的學習。本文僅供學習使用,勿作他用,如侵犯權益,請聯繫我,立即刪除。

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