1. 程式人生 > >資料結構與算法系列15(中)--散列表(雜湊表)

資料結構與算法系列15(中)--散列表(雜湊表)

如何設計一個雜湊函式?

1.雜湊函式的設計不能太複雜,否則會消耗很多計算時間,也就影響了散列表的效能。
2.雜湊函式生成的值要儘可能的隨機並且均勻分佈,這樣才能最小化雜湊衝突,即便發生衝突,雜湊到每個槽裡的資料也會比較平均,不會出現某個槽裡資料太多的情況。

裝載因子的選擇

上一節我們講過,裝載因子越大,說明散列表中的元素越多,空閒位置越小,雜湊衝突的概率就越大。那我們怎樣解決這個問題呢?可以通過設定裝載因子的閾值來控制是擴容還是縮容,設計一個支援動態擴容的散列表。這裡的裝載因子的選擇要權衡各個方面的因素,如果選擇的值過大,會導致衝突過多,如果選擇的值太小,會導致記憶體的嚴重浪費。所以我們選擇裝載因子的伐值時一定要權衡時間、空間複雜度,如果記憶體空間不緊張,對執行效率要求很高,可以降低裝載因子的伐值。相反,如果記憶體空間不緊張,對執行效率要求不高,可以增加裝載因子的值。

如何避免低效的擴容

我們知道,當裝載因子已經達到伐值,需要先進行動態擴容,搬移舊資料,再插入新資料。如果此時的資料量非常大,那插入資料的過程就會變得特別慢,甚至無法接受。這樣一次性擴容的操作,確實耗時過多,那麼我們有什麼好的方法來解決這個問題嗎?事實上我們可以將擴容的過程穿插在插入資料的過程中,分批來完成。
具體過程:
當我們要插入一個新的資料時,如果裝載因子已經達到伐值,我們先申請一個新的擴容後的空間,注意此時我們不做舊資料的搬移,而是直接將新資料插入到新的散列表中,然後從老的散列表中取出一個數據放到新的散列表中。以後每插入一個新的資料,我們都重複上面的過程,經過多次的插入操作後,老的散列表的資料就一點一點地全都搬移到新的散列表中。這樣我們就沒有了集中一次性插入資料和搬移資料,整個插入過程就會變得非常快。通過這種方式,任何情況下,插入資料的時間複雜度都是O(1)。
有人可能會有疑問,那中間的查詢操作怎麼辦呢?


對於查詢操作,為了相容舊的和新的散列表,我們可以先在新的散列表中查詢,如果沒有找到,就在舊的散列表中查詢。

如何選擇雜湊衝突的解決辦法

1.開放定址法:
當資料量較小,裝載因子較小時,適合採用開發定址法。
2.連結串列法:
大部分情況下,連結串列法用的比較普遍。比較適合儲存大物件,大資料量的散列表,而且,比起開放定址法,它更加靈活,支援更多的優化策略,比如用紅黑樹代替連結串列,來避免散列表時間複雜度退化成O(n),抵禦雜湊碰撞攻擊。

如何設計一個工業級的雜湊函式

先提一個問題,何為一個工業級的散列表?工業級的散列表應該具有哪些特性?
1.支援快速的查詢、插入、刪除操作;
2.記憶體佔用合理,不能浪費過多的記憶體空間;
3…效能穩定,在極端情況下,散列表的效能也不會退化到無法接受的情況。
具體方案:(如何設計這樣一個散列表呢)


1.設計一個合適的雜湊函式;
2.定義裝載因子閾值,並且設計動態擴容策略;
3.選擇合適的雜湊衝突解決方法。
關於雜湊函式、裝載因子、動態擴容策略,還有雜湊衝突的解決辦法,我們前面都講過了,具體如何選擇,還要結合具體的業務場景、具體的業務資料來具體分析。
4.對於動態散列表來說,不管我們如何設計雜湊函式,選擇什麼樣的雜湊衝突解決方法。隨著資料的不斷增加,散列表總會出現裝載因子過高的情況。這個時候,我們就需要啟動動態擴容。