資料結構和演算法之 雜湊表下

2021-09-13 03:40:52 字數 2780 閱讀 3959

雜湊表和煉表經常組合起來使用,但它們是如何組合起來使用的,為什麼它們會經常一塊使用呢?
基於鍊錶實現 lru 快取淘汰演算法的原理是這樣的:我們維護乙個有序單鏈表,越靠近鍊錶頭部的結點是越早訪問的。當有乙個新的資料被訪問時,我們從煉表頭開始順序遍歷鍊錶。

如果此資料之前已經被快取在鍊錶中了,我們將其從原來的位置刪除,然後再插入到鍊錶的尾部。

如果此資料沒有快取在鍊錶中,又可以分為兩種情況:

因為不管快取是否已滿,我們都需要遍歷一遍鍊錶,因此,基於鍊錶實現的快取訪問的時間複雜度為 $o(n)$。

乙個快取(cache)系統主要包含下面這幾個操作:

如果我們將雜湊表和煉表兩種資料結構結合起來使用,可以將這幾個操作的時間複雜度都降低到 $o(1)$。

具體的結構就是下面這個樣子:

使用雙向鍊錶來儲存資料,鍊錶中的每個結點包括資料(data)、前驅指標(prev)、後繼指標(next)還有乙個特殊的 hnext 指標。

因為我們使用鍊錶法來解決雜湊衝突,所以每個結點都會在兩條鏈中存在。乙個鏈是上面的雙向鍊錶,另乙個鏈則是雜湊表中雜湊值相同的元素組成的拉鍊。前驅和後繼指標是為了將結點串在雙向鍊錶中,hnext 指標是為了將結點串在雜湊表的拉鍊中。

查詢資料的時候,我們通過雜湊表可以在時間複雜度接近於 $o(1)$ 內找到乙個資料,然後,我們再將其移動到雙向鍊錶的尾部。

刪除資料的時候,我們在時間複雜度接近於 $o(1)$ 內找到要刪除的結點,然後由於是雙向鍊錶,我們可以直接得到前驅指標,刪除結點也只需要 $o(1)$ 的時間複雜度。

新增資料的時候,類似於單鏈表的情況,我們也可以在 $o(1)$ 時間複雜度內完成。

而其他操作,比如刪除頭結點、尾部插入資料等,都可以在 $o(1)$ 時間複雜度內完成。因此,我們就通過雜湊表和雙向鍊錶的組合使用,實現了乙個高效的、支援 lru 快取淘太演算法的快取系統原型。

在 跳表 中,我們實現了乙個簡單的有序集合。但實際上,在有序集合中,每個成員物件有兩個重要的屬性,key (鍵值)和 score (分值)。我們不僅會通過 score 來查詢資料,還會通過 key 來查詢資料。

因此 redis 有序集合的操作主要有以下幾種:

如果我們僅僅按照分值將成員物件組織成跳表的結構,那按照鍵值來刪除、查詢成員物件就會很慢,解決方法與 lru 快取淘太演算法的解決方法類似。我們可以再按照鍵值構建乙個雜湊表,這樣按照鍵值來刪除、查詢成員物件的時間複雜度就變成了 $o(1)$。

hashmapm = new linkedhashmap<>();

m.put(3, 11);

m.put(1, 12);

m.put(5, 23);

m.put(2, 22);

for (map.entry e : m.entryset())

這段**的輸出是 3, 1, 5, 2,你有沒有覺得奇怪?雜湊表中的資料是經過雜湊函式打亂之後無規律儲存的,這裡是如何按照資料的插入順序來遍歷輸出的呢?

其實,linkedhashmap 也是通過雜湊表和煉表結合在一起實現的。實際上,它不僅支援按照插入順序遍歷資料,還支援按照訪問順序來遍歷資料

// 10 是初始大小,0.75 是裝載因子,true 是表示按照訪問時間排序

hashmapm = new linkedhashmap<>(10, 0.75f, true);

m.put(3, 11);

m.put(1, 12);

m.put(5, 23);

m.put(2, 22);

m.put(3, 26);

m.get(5);

for (map.entry e : m.entryset())

這段**的輸出是 1, 2, 3, 5,我們來具體看一下。

每次呼叫 put() 函式,都會將資料新增到鍊錶的尾部,前四個操作後,鍊錶中的資料是下面這樣:

在第八行,當我們再次將鍵值為 3 的資料放入到 linkedhashmap 中去的時候,就會先查詢這個鍵值是否已經存在。然後,將已經存在的 (3, 11) 刪除,並將新的 (3, 26) 放到鍊錶尾部。

在第九行,當我們訪問鍵值為 5 的資料的時候,我們將被訪問的資料移動到鍊錶尾部。

可以看到,按照訪問時間排序的 linkedhashmap 本身就是乙個支援 lru 快取淘汰策略的快取系統。 linkedhashmap 中的 linked 實際上指的是雙向鍊錶。

雜湊表這種結構雖然支援非常高效的資料插入、刪除、查詢操作,但是雜湊表中的資料都是通過雜湊函式打亂之後無規率儲存的。也就是說,它無法支援按照某種順序快速地遍歷資料。

如果希望按照順序遍歷雜湊表中的資料,那我們需要將雜湊表中的資料拷貝到陣列中,然後排序遍歷。

但是,雜湊表是動態資料結構,需要不停地插入、刪除資料,若每次遍歷資料都需要先排序,那效率勢必很低。

為了解決這個問題,我們就將雜湊表和煉表(或者跳表)結合在一起使用。

參考資料-極客時間專欄《資料結構與演算法之美》

資料結構之雜湊表(雜湊表)

今天學的是資料結構的雜湊查詢篇,其他的查詢可參見以前的傳送門 以前的查詢都是基於比較關鍵字的基礎上,所以查詢的效率依賴於查詢過程中所進行的比較次數。理想的情況是不經過任何比較,通過計算就能直接得到記錄所在的儲存位址,雜湊查詢 hashed search 是基於上述思想的一種查詢方式。雜湊法又稱為雜湊...

資料結構與演算法之雜湊表

介紹 雜湊表 hash table,也叫雜湊表 是根據關鍵碼值 key value 而直接進行訪問的資料結構。也就是說,它通過把關鍵碼值對映到表中乙個位置來訪問記錄,以加快查詢的速度。這個對映函式叫做雜湊函式,存放記錄的陣列叫做雜湊表。應用 看乙個實際需求,google公司的乙個上機題 有乙個公司,...

《資料結構和演算法分析》學習之雜湊表

這裡只是簡單的介紹一下,了解一下其比較強大的功能。肯定還有很多比較使用改進方法,需要多學習才行。在進行查詢的過程中,無論是線性查詢還是二分查詢,其對都是時間的函式乙個是o n 乙個是o log2n 而雜湊表進行查詢的時候,時間函式是o 1 其對時間來說是個常數,跟資料的規模沒有關係。其主要通過直接索...