此處使用分離鏈接法來解決衝突,即將散列到同一個桶的所有元素都保存到一個鏈表中。
對象必須實現 hashCode() 方法(計算哈希值)和 equals() 方法(判斷是否已經存在該元素)。
除了鏈表,也可以使用其他的數據結構來解決衝突,如,二叉搜索樹、另一個散列表 …
但是,我們期望如果散列表足夠大且散列函數可以將元素分佈均勻,那麼所有的鏈表都應該是短的,此時使用簡單的鏈表就足以。
裝填因子
定義裝填因子 ,即每個衝突鏈表的平均長度爲 。
-
在一次失敗的查找中,要探查的節點數平均爲 :先根據哈希值定位到一個哈希桶,然後再遍歷對應的衝突鏈表(平均含 個節點)。
-
在一次成功的查找中,要探查的節點數平均爲 :先根據哈希值定位到一個哈希桶,然後再遍歷對應的衝突鏈表,平均來看,有一半的“其他”節點被檢查到,接着就是目標節點。
由此來看,散列表的大小並不重要,裝填因子纔是重要的!
再散列
就是重新構建一個散列表,然後再重新插入每一個元素至新的散列表中。
其運行時間爲 O(N) 。
在 rehash 之前,必然已存在 N 次插入操作,因此,插入操作的時間複雜度在均攤意義上爲 O(1) 。
import java.util.LinkedList;
import java.util.List;
public class SeparateChainingHashTable<T> {
private static final int DEFAULT_TABLE_SIZE = 101; // 哈希表默認大小
private List<T> [] lists; // 鏈表:解決衝突
private int size; // 當前元素個數
private float loadFactor; // 裝填因子 = 元素總數 / 哈希表桶數,即,size / lists.length
public SeparateChainingHashTable() {
this(DEFAULT_TABLE_SIZE, 1.0f);
}
public SeparateChainingHashTable(int size, float loadFactor) {
lists = new LinkedList[nextPrime(size)];
for (int i = 0; i < lists.length; i++) {
lists[i] = new LinkedList<>();
}
this.loadFactor = loadFactor;
}
// 計算元素的哈希值
private int hash(T e) {
int key = (e.hashCode() + lists.length) % lists.length;
return key;
}
// 大於等於 value 的最小素數
private int nextPrime(int value) {
while (!isPrime(value)) {
if (value >= Integer.MAX_VALUE) {
break;
}
value++;
}
return value;
}
private boolean isPrime(int value) {
int last = (int) Math.sqrt((double) value);
for (int i = 2; i <= last; i++) {
if (value % i == 0) {
return false;
}
}
return true;
}
// 是否包含元素 e
public boolean contains(T e) {
List<T> list = lists[hash(e)];
return list.contains(e);
}
// 插入元素 e:成功時返回 true
public boolean insert(T e) {
List<T> list = lists[hash(e)];
if (list.contains(e)) {
return false;
}
list.add(0, e);
size++;
// 是否需要 rehash
if ((int) (size * loadFactor) > lists.length) {
rehash();
}
return true;
}
// 重哈希:桶數大致變爲之前的兩倍
private void rehash() {
List<T> [] oldLists = lists;
lists = new LinkedList[nextPrime(lists.length << 1)];
for (int i = 0; i < lists.length; i++) {
lists[i] = new LinkedList<>();
}
// 重新插入:耗時
for (int i = 0; i < oldLists.length; i++) {
for (T e : oldLists[i]) {
insert(e);
}
}
}
// 刪除元素 e
public void remove(T e) {
List<T> list = lists[hash(e)];
if (list.contains(e)) {
list.remove(e);
size--;
}
}
// 根據 e 的哈希值獲取對應的元素
public T get(T e) {
List<T> list = lists[hash(e)];
for (int i = 0; i < list.size(); i++) {
if (e.equals(list.get(i))) {
return list.get(i);
}
}
return null;
}
}