算法導論十一章:散列表

實現字典的一種有效的數據結構爲散列表,在最壞的情況下,在散列表中查找一個元素的時間與在鏈表中查找一個元素的時間相同,爲Θ(n)。在實踐中,散列技術的效率是很高的,在一些合理的假設下,在散列表中查找一個元素的期望時間爲O(1)。散列表是普通數組概率的推廣。

 

11.1直接尋址表

 

當關鍵字的全域U比較小時,直接尋址時一種簡單而有效的技術。假設某個動態集合都一個取自全域U={0,1,...,m-1}的關鍵字,假設沒有兩個元素具有相同的關鍵字。

 

我們用一個數組T[0...m-1](直接尋址表),每個位置對應全域U中的一個關鍵字;槽k指向集合中一個關鍵字爲爲k的元素,如果該集合沒有關鍵位k的元素,則T[k]=NIL。

 

11.2散列表

 

如果U很大的話,直接尋址表就不適用了,而且如果存儲的關鍵集合K相對U來說可能很小,分配的大部分空間都要浪費掉。

 

在直接尋址方式下,具有關鍵字k的元素存放在槽k中。在散列方式下,該元素處在h(k)中。亦即利用散列函數h,依據關鍵字k計算出槽的位置。

                                                      h: U-->{0,1,...,m-1}

採用散列函數的目的就在於縮小需要處理的下標範圍,我們要處理的值從|U|降到m了,從而降低了空間開銷。

 

散列表有一個問題就是不同的關鍵字可能映射到同一個槽上,這種情況叫做碰撞。最理想的解決方法就是完全避免碰撞,選用合適的h。在選擇時有一個主導思想就是h近可能地隨機,從而避免或最大限度避免碰撞,術語“散列”即體現了這種精神。當|U|>m時,要完全避免碰撞時不可能的。

 

鏈接法解決碰撞

 

鏈接法,把散列到同一槽中所有元素都放在一個鏈表中,槽j中有一個指針,它指向所有散列到j的元素構成的鏈表的頭。

 

給定一個能存放n個元素、具有m個槽位的散列表T,定義T的裝載因子爲α=n/m。

 

散列方法的平均性能依賴於所選取的散列函數h在一般情況下將所有關鍵字分佈在m個槽位上的均勻程度。假設任意元素散列到m個槽中的每一個的可能性是相同的,且與其他元素已被散列到什麼位置上是獨立無關的,稱這個假設爲“簡單一致散列”。

 

在這種情況下查找的期望時間是Θ(1+α)。

 

 11.3散列函數

這一節討論三種散列函數設計方案,前兩種都是啓發式方法,第三種方法則利用了隨機化的技術,提供可證明的良好性能。

 

好的散列函數的特點

 

一個號的散列函數應滿足“簡單一致散列的假設”。一般情況下不太可能檢查這一條件是否成立,因爲很少能知道關鍵字所符合的概率分析,而各關鍵字可能並不是完全相互獨立的。

 

在實踐中,常常可以運用啓發式技術來構造好的散列函數,在設計過程中可以利用有關關鍵字的分佈的限制信息。例如在一個編譯器的符號表,關鍵字都是字符串,表示程序中的標誌符,在同一個程序中經常會出現一些很相近的符號,比如pt和pts。好的散列函數應該能夠最小化將這些相近符號散列到同一個槽中的可能性。

 

一種好的做法是獨立於“數據中可能存在的任何模式”方式到處散列值。

 

將關鍵字解釋爲自然數

 

許多散列函數都假定關鍵字域爲自然數集N。如果所給關鍵字不是自然數,則必須有一種方法來將它們解釋爲自然數。例如標識符pt可以解釋爲十進制整數對(112,116),ASCII字符值,然後pt即爲112*128+116=14452。

 

11.3.1除法散列法

通過取k除以m的餘數,來將關鍵字k映射到m個槽中的某一個: h(k) = k mode m。

除法散列要注意m的選擇,m不應是2的冪,否則h(k)就是k的p個最低位。

 

通常選擇m爲與2的整數冪不太接近的質數。

11.3.2乘法散列法

構造散列函數的乘法方法包含兩個步驟。第一步,用關鍵字k乘上常數A(0<A<1),並取出k*A的小數部分。然後用m乘以這個值,再取底。

這個方法對m的選擇沒有什麼要求,一般選擇它爲2的某個冪2p。可以按如下方法實現,假設計算機的字長爲w位,而k正好可以容於一個字中。限制A爲形如S/2w的一個分數。先用w位整數S乘上k,其結果是一個2w位的值r1*2w+r0,其中r1爲乘積的高位字,r0爲低位字。r0的p個最高有效位就是h(k)。

 

Knuth認爲A=(√5-1)/2=0.6180339887...是一個比較理想的值。

11.3.3全域散列

如果讓某個與你作對的人來選擇要散列的關鍵字,那麼他會選擇全部散列到同一個槽中的n個關鍵字。任何一個特定的散列函數都可能出現這種最壞情況。唯一的有效改進方法時隨機地選擇散列函數,使之獨立於要存儲的關鍵字。這種方法稱爲“全域散列”。

 

全域散列的基本思想就是在執行開始時,從一族仔細設計的函數中,隨機選定一個作爲散列函數。只有恰好選擇了一個與即將存儲的關鍵字形成較壞性態的散列函數,纔會出現差的性能。

 

設H爲有限的一族散列函數,它將關鍵字域U映射到{0,1,...,m-1}。這樣一族函數稱爲全域的:對每一對不同的關鍵字k,l,滿足h(k)=h(l)的散列函數h∈H的個數至多爲|H|/m。換言之,如果隨機地選擇一個散列函數,兩個關鍵字發生碰撞的概率不大於1/m。可以證明每個槽鏈表的期望至多爲1+α。

 

設計全域散列函數集:

選一個足夠大的質數p,使得每個可能的關鍵字k都落在0到p-1的範圍內。設Zp={0,1...,p-1},設Z*p={0,1,...,p-1}。取a∈Zp,b∈Z*p,定義函數ha,b(k)= ((ak+b) mod p) mod m,Hp,m函數族共有p(p-1)個函數。

11.4 開放尋址法

解決碰撞的另一張方法就是開放尋址法,所有的元素都放在散列表槽中。當要插入一個元素時,可以連續地檢查散列表的各項,知道找到一個空槽。檢查的順序不一定是{0,1,...,m-1}。將散列函數加以擴充,使之包含探查號作爲第二個輸入參數,散列函數變成: h: U x {0,1,...,m-1} --> {0,1,...,m-1}。

對開放尋址法來說,要求每個關鍵字的探查序列 { h(k,0), h(k,1),...,h(k,m-1)}必須是{0,1,...,m-1}的一個排列。

 

HASH-INSERT(T,k)

1   i<--0

2   repeat j <-- h(k,i)

3        if T[j]=NIL

4           then  T[j]<--k

5                return j

6           else i<--i+1

7    until i=n

8   error "hash table overflow"

 

有三種技術常用來計算開放尋址探查序列:線性探查,二次探查,以及雙重探查。

 

線性探查

    給定一個普通的散列函數h'(k), 線性探查採用的散列函數爲h(k,i) = (h'(k)+i) mod m。

    線性探查存在“一次羣集”現象。當一個空槽前有i個滿槽時,該空槽爲下一個將被佔用槽的概率爲(i+1)/m。連續被佔用的槽會越來越長,因而平均查找時間會隨之增加。

 

二次探查

“二次探查”採用如下形式散列函數

   h(k,i) = (h'(k) + c1i + c2i2) mod m

 

雙重散列

雙重散列是用於開發尋址法德最好方法之一,它產生的排列具有隨機選擇的許多特性,它採用如下形式的散列函數:

 h(k,i)  = ( h1(k) + i*h2(k) ) mod m

與線性探查和二次探查不同的是,這裏的探查序列以兩種方式依賴於關鍵字k。

11.5 完全散列

 如果某種散列技術在進行查找時其最壞情況內存訪問次數爲O(1),則稱其爲完全散列。

設計完全散列方案的基本想法就是採用一種兩級的散列方案,每一級採用全域散列。

 

 

 

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