本文根據《大話數據結構》一書,實現了Java版的一個簡單的散列表(哈希表)。
基本概念
對關鍵字key,將其值存放在f(key)的存儲位置上。由此,在查找時不需比較,只需計算出f(key)便可直接取得所查記錄。這個函數 f() 就叫做散列函數,按這個思想建立的表稱為散列表。
散列技術即是一種存儲方法,又是一種查找方法:
存儲過程:根據關鍵字key,算出f(key),將記錄存放在f(key)的位置上;
查找過程:根據關鍵字key,算出f(key),該位置上的值即為要找的記錄。
散列函數的構造方法
直接定址法
直接取關鍵字的線性函數為散列地址:f(key)=a×key+b(a,b為常數)
如:對下表的記錄,關鍵字key取為出生年份,令f(key)=key-1980即可。
數字分析法
分析一組數據,找出其規律,盡可能利用這些數據來構造沖突幾率較低的散列地址
如:以員工的手機號碼作為關鍵字,前7位數字基本相同,可以選擇后面四位數字作為散列地址。
平方取中法
當無法確定關鍵字中哪幾位分布較均勻時,可以先求出關鍵字的平方值,然后按需要取平方值的中間幾位作為散列地址。
折疊法
將關鍵字分割成位數相同的幾部分,最后一部分位數可以不同,然后取這幾部分的疊加和(去除進位)作為散列地址。
除留余數法
最為常用的方法,取關鍵字被某個不大於散列表表長m的數p除后所得的余數為散列地址。
f(key) = key MOD p,p<=m。
隨機數法
選擇一隨機函數(偽隨機),取關鍵字的隨機值作為散列地址,通常用於關鍵字長度不同的場合。
處理散列沖突的方法
當兩個關鍵字key1和key2不同時,有f(key1)=f(key2),這種現象稱為沖突。一般情況下,我們會盡量設計恰當的散列函數減少沖突,但無法完全避免,這就需要對沖突進行處理。
開放尋址法
一旦發生沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入。根據下一個位置的不同,又可分為以下三種:
①線性探測法:
②二次探測法
③隨機探測法
再散列函數法
在同義詞產生地址沖突時計算另一個散列函數地址,直到沖突不再發生,這種方法不易產生“聚集”,但增加了計算時間。如下圖所示(RHi代表不同的散列函數):
鏈地址法
相同地址的記錄存放在一個單鏈表中,散列表值存儲所有同義詞子表的頭指針。如下圖所示:
公共溢出區法
為所有沖突的關鍵字建立一個公共的溢出區來存放。
代碼實現
接下來建立一個簡單的散列表,其散列函數采用上述的除留余數法,處理沖突的方法采用開放定址法下的線性探測法。
Java代碼如下:
package HashTable;
/**
* 散列表
* @author Yongh
*
*/
public class HashTable {
int[] elem;
int count;
private static final int Nullkey = -32768;
public HashTable(int count) {
this.count = count;
elem = new int[count];
for (int i = 0; i < count; i++) {
elem[i] = Nullkey; // 代表位置為空
}
}
/*
* 散列函數
*/
public int hash(int key) {
return key % count; // 除留余數法
}
/*
* 插入操作
*/
public void insert(int key) {
int addr = hash(key); // 求散列地址
while (elem[addr] != Nullkey) { // 位置非空,有沖突
addr = (addr + 1) % count; // 開放地址法的線性探測
}
elem[addr] = key;
}
/*
* 查找操作
*/
public boolean search(int key) {
int addr = hash(key); // 求散列地址
while (elem[addr] != key) {
addr = (addr + 1) % count; // 開放地址法的線性探測
if (addr == hash(key) || elem[addr] == Nullkey) { // 循環回到原點或者到了空地址
System.out.println("要查找的記錄不存在!");
return false;
}
}
System.out.println("存在記錄:" + key + ",位置為:" + addr);
return true;
}
public static void main(String[] args) {
int[] arr = { 12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34 };
HashTable aTable = new HashTable(arr.length);
for (int a : arr) {
aTable.insert(a);
}
for (int a : arr) {
aTable.search(a);
}
}
}
存在記錄:12,位置為:0存在記錄:67,位置為:7存在記錄:56,位置為:8存在記錄:16,位置為:4存在記錄:25,位置為:1存在記錄:37,位置為:2存在記錄:22,位置為:10存在記錄:29,位置為:5存在記錄:15,位置為:3存在記錄:47,位置為:11存在記錄:48,位置為:6存在記錄:34,位置為:9
HashTable
代碼中重點可以看:插入操作是如何處理沖突 以及查找操作是如何判斷記錄是否存在的。