散列表
- 目的:如果所有的鍵都是小整數,可以用一個數組來實現無序的符號表,將鍵作爲數組的索引而數組中鍵
i
處存儲的就是它對應的值。
- 查找算法步驟
- 用散列函數將查找的函數轉化爲數組的一個索引。理想情況下,不同的減可以轉換爲不同的索引值。
- 處理碰撞衝突,分爲拉鍊法和線性探測法。
- 散列表是算法在時間和空間上的均衡處理方式。
散列函數
- 散列函數特點:散列函數應該易於計算並且能夠均勻分佈所有的鍵,即對於任意的鍵,所給出的散列值應得呈隨機性。
- 散列函數與鍵的類型有關。
- 正整數:正整數散列最常用的方式爲除留餘數法。選擇一個
M
的素數的數組,對於任意正整數k
,計算k%M
。如果不是素數,可能無法獲得均勻的散列值。
- 浮點數:如果鍵值是0到1之間,可以將鍵值乘以素數M後,四捨五入得到一個0到M-1之間的索引值。但這樣會使鍵的高位起作用更大,低位鍵值不影響散列值。解決辦法是,鍵值用二進制表示後使用除留餘數法。
- 字符串:字符串散列同樣使用除留餘數法,將字符串考慮爲大整數即可。Java的
charAt()
函數能夠返回一個非負16位整數。如果R是比任何字符都大的整數,相當於,將字符串當做一個N位的R進制值,將它除以M求餘。Horner方法,用N次乘法、加法和取餘來計算一個字符串的散列值。只要R足夠小,不造成溢出。
java
int hash = 0;
for (int i = 0; i < s.length(); i++)
hash = (R * hash + s.charAt(i)) % M
- 組合鍵:如果鍵的類型包含多個整形變量,我們可以和String類型一樣將他們混合起來。例如,被查找的類型是Date,其中整型域:day(兩位數)、month(兩位數)和year(四位數),散列值是:
Java
int hash = (((day * R + month) % M) * R + year) % M;
- 只要R足夠小不造成溢出,也可以得到0到M-1之間的散列值。同時選取適當素數M值,例如31,來省去括號內的
%M
計算。
- Java約定:即散列值的硬性規則。由於每種數據類型都需要相應的散列函數,於是Java讓所有數據類型都繼承了一個能夠返回32bit整數的hashCode()方法。因此若
a.equals(b)
的值爲ture
,那麼對應的hashCode值也應相同,若hashCode值不同,只能說明看似相同的數據,實則爲不同數據類型。但同hashCode值,兩個對象可能不同。
- 將hashCode的返回值轉化爲數組索引:若我們想使用短的索引值,而不是32bit整數的散列值,這裏利用
hashCode()
方法和除留餘數法結合,產生0到M-1的整數。
private int hash(Key x){
return (x.hashCode() & 0x7ffffffff) % M;
}
- 自定義的hashCode()方法:可以按照將hashCode的返回值轉化爲短整數的方法,進行變換。將對象的每個變量的hashCode()返回值轉化爲32位整數並計算得到散列值。
public class Transaction{
...
private final String who;
private final Date when;
private final double amount;
public int hashCode(){
int hash = 17;
hash = 31 * hash + who.hashCode();
hash = 31 * hash + when.hashCode();
hash = 31 * hash + ((Double) amount).hashCode();
return hash;
}
...
}
- 軟緩存:如果散列值的計算很耗時,可以將計算的散列值存儲在每個鍵的散列值函數裏,即利用hash變量存儲hashCode()的返回值。
基於拉鍊法的散列表
基於線性探測法的散列表