第 5章 雜湊
第 5章 雜湊
散列表的實現常常叫做雜湊(hashing),雜湊是一種用於以常數平均時間執行插入、刪除和查詢的技術,但是,那些需要元素見任何排序資訊的樹操作將不會得到有效的支援
5.1 一般想法
理想的散列表資料結構只不過時包含一些項的具有固定大小的陣列。
5.2 雜湊函式
如果輸入的關鍵字是整數,則一般合理的方法就是直接返回key mod tablesize ,除非key具有某些不合乎需要的性質,
5.3 分離連結法
解決衝突的第一種方法叫做分離連結法 其做法是將雜湊到同一個值的所有元素儲存到一個表中,我們可以使用標準庫表的實現方法,如果空間很緊,則更可取的方法是避免使用他們因為(這些表是雙向連結串列並且浪費空間)
填充因子 為散列表中元素個數對錶的大小的比
連結串列的平均長度為λ
執行一次查詢所需要的工作就是計算雜湊函式值所需要的常數時間加上遍歷表所用的時間,
一次成功的查詢需要遍歷大約 1+(λ/2)個鏈
5.4 不用連結串列的散列表
分離連線雜湊演算法的缺點是使用一些連結串列,由於給新單元分配地址需要時間(特別是在其他語言中),因此這就導致演算法的速度有些減慢,同時演算法實際上還要求第二種資料結構才能實現 另一種不用連結串列的解決衝突的方法,嘗試另外的一些單元,知道找出空的單元為之,
這種解決問題的方式導致所需要的表比分離連結雜湊的表大,其填充因子應該低於0.5
5.4.1 線性探測法
線上性探索法中,函式f
只要表足夠大,總能找到一個自由的單元,但是如此花費的事件相當的多,更糟糕的是,即使表相對較空,這樣佔據的單元也會開始形成一些區塊,其結果成為一次聚集
5.4.2 平方探測法
平方探測法是消除線性探測中一次聚集問題的衝突解決方法,平方探測就是衝突函式為2次的探測方法
但表的大小超過一半時,當表的大小不是素數時,甚至在表被填充一半之前就不能保證一次就找到空的單元了
表的大小是素數非常重要
在探測散列表中標準的刪除操作不能執行,因為相應的單元可能已經引起衝突,所有探測散列表中只能惰性刪除
二次聚集 雖然平方探測排除了一次聚集,但是雜湊到同一個位置上的那些元素將探測相同的備選單元,所以這叫做二次聚集。
5.4.3 雙雜湊
f(i) = i* hash2(x)我們將在第二個雜湊函式應用到x並距離hash2(x),2hash2(x)
5.5 再雜湊
為了覺得平方探索,操作時間過長和插入失敗的問題,提供了一種解決方法 建立另外一個大約兩倍大的表(而且使用一個相關的新雜湊函式),掃描整個原始散列表,並計算每個(未刪除)元素的新雜湊值並將其插入到新表中
再雜湊可以用平方探測以多種方法實現 一種做法是表滿裝到一半 2 極端做法 插入失敗時再雜湊,第三種是 到達某一個裝填因子就進行雜湊
5.6 標準庫中的散列表
因為散列表操作中最費時間的部分就是計算hashCode方法,所有String類對hashCode方法進行了優化, 每一個String物件都在儲存它的hashCode值,該預設值是0 但若hashCode被呼叫,那麼這個值就會被記住。因此當可以避免hashCode對String物件進行兩次計算,因此避免 昂貴的重新計算 這個技巧叫做快閃記憶體雜湊程式碼
快閃記憶體雜湊程式碼之所以有效,是因為String類時一個不可變類
5.7 最壞情況下O(1)訪問散列表
當有合理的裝填因子時和合理的雜湊函式時,可以期望插入、刪除和查詢的平均花銷都是O(1)
5.7.1 完美雜湊
每一個二級散列表將用一個不同的雜湊函式進行構造,知道沒有衝入妹紙,如果產生的衝突次數高於要求值,主散列表也可以被構建多次,這種方法稱為完美雜湊
5.7.2 布穀鳥雜湊
在布穀鳥雜湊中,假設有N個項,我們維護兩個分別超過一半空的表,而且有兩個獨立的雜湊函式,可以把每一項分配到每一個表中的位置,布穀鳥是雜湊中總是會被儲存在這兩個位置之一
於是焦點問題就在於,存在阻礙插入成功的迴圈概率有多大,以及成功插入需要替換次數的期望是多少,裝填因子小於0.5的時候替換次數的期望是一個常數。
因此在若干次替換被檢測到後,我們就可以簡單的用新的雜湊函式重新建表,更N次插入以重新建表,即便如此 也意味著這種花費很,然而表的裝填因子達到0.5或者更高,迴圈的概率就更高,這種方法就不太好用了
5.7.3 跳房子雜湊
跳房子雜湊的思路是 用事先確定的 對計算機底層體系結構而言是最優秀的一個常數 給探測序列的最大長度加一個上屆。這樣做可以給常數的最壞查詢時間,並且與布穀鳥雜湊一樣,查詢並優化,以同時檢查可用位置的有限集
如果某次插入要把一個新的項放到距離它的雜湊位置太遠的地方,我們會很有效的掉頭想雜湊位置走,替換掉潛在項,如果足夠謹慎,那麼替換可以很快完成,並且保證那些被替換的想都不會放到距離它們的雜湊位置很遠的地方
5.8 通用散列表
儘管散列表很有效,並且子啊裝填因子適當的情況下,假設每個操作都有固定的平均花銷,但是其表現和分析卻取決於雜湊函式具有以下兩種性質
1. 雜湊函式必須可在常數時間內計算
2. 雜湊函式必須將各項均勻分佈在陣列單元中
以你雜湊函式不好,一切都是徒勞。每個操作的花銷都可能是線性的
5.9 可擴雜湊
本節討論資料量太大,以至於裝不進主存的情況,此時主要考慮是檢索資料所需的磁碟存取次數
可擴雜湊 它使得用兩次磁碟訪問執行一次查詢,插入操作也需要很少的磁碟訪問
小結
1. 散列表可以用來以常數平均時間實現insert 和查詢操作,當使用散列表時應該注意 注入裝填因子 否則時間界將不再有效。仔細選擇雜湊函式也很重要
2. 對於分離連結雜湊法,雖然裝填因子不是很大時效能並不明顯降低,但裝填因子還是應該接近1 。對於探測雜湊演算法,除非完全不可避免,否則裝填因子不應該超過0.5 如果使用線性探測,那麼線性隨著裝填因子接近1將急速下降。
二叉樹也可以用來實現insert和contains運算,雖然平均時間界為0(log N)但是二叉樹也支援哪些需要序從功能更大的例程,使用散列表不能找出最小元素。
另一方面雜湊的最壞情況一般來自於實現的錯誤。而有序的輸入卻可能使二叉樹執行的很差。平衡查詢樹實現代價很高,因此在不需要使用有序的資訊以及對輸入是否被排序存在有懷疑,都應該選擇雜湊這種資料結構
雜湊有豐富的應用,編譯器使用散列表追蹤程式碼中宣告的變數,這種資料結構叫做符號表
對於任何帶有實際名字而非數字節點的圖論問題,散列表都是有用的