此处使用分离链接法来解决冲突,即将散列到同一个桶的所有元素都保存到一个链表中。
对象必须实现 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;
}
}
来源:CSDN
作者:W.T.F.
链接:https://blog.csdn.net/fcku_88/article/details/104142416