不用链表的散列表
分离链接法的缺点
使用了一些链表。给新单元分配地址需要时间,导致算法的速度会有些慢,同时算法还要求对第二种数据结构的实现。
而,不用链表解决冲突的方法是:尝试另外的单元,直到找到空的单元。
装填因子
散列表的装填因子(load factor) 为散列表中的元素个数对该表大小的比。
分离链接法的因子为1。
探测散列表:因子小于0.5
探测散列表
线性探测法
线性探测法会存在一次聚集的问题
平方探测法
平方探测法排除了一次聚集,但是仍会存在二次聚集,采用双散列的方法来解决。
平法探测法Java实现
package com.hash;
/**
* 基于平方探测法的散列表
*/
public class QuadraticProbingHashTable<Item> {
private static final int DEFAULT_TABLE_SIZE = 101; // 默认表大小
private HashEntry<Item>[] array;// 散列表
private int occupied; // 占据单元个数
private int theSize; // 当前位置
/*
* 用来装数据,标记是否active(惰性删除)
*/
private static class HashEntry<Item> {
public Item element; // 元素
public boolean isActive; // 如果被标记为删除,返回false
public HashEntry(Item e) {
this(e, true);
}
public HashEntry(Item e, boolean i) {
element = e;
isActive = i;
}
}
/*
* 构造
*/
public QuadraticProbingHashTable() {
this(DEFAULT_TABLE_SIZE);
}
public QuadraticProbingHashTable(int size) {
allocateArray(size);
doClear();
}
/*
* 分配数组
*/
private void allocateArray(int arraySize) {
array = new HashEntry[nextPrime(arraySize)]; // 大于size的素数作为表长度
}
/*
* 插入
*/
public boolean insert(Item x) {
// Insert x as active
int currentPos = findPos(x);
if (isActive(currentPos))
return false;
if (array[currentPos] == null)
++occupied;
array[currentPos] = new HashEntry<>(x, true);
theSize++;
// 再散列
if (occupied > array.length / 2)
rehash();
return true;
}
/*
* 扩展hash表
*/
private void rehash() {
HashEntry<Item>[] oldArray = array;
// Create a new double-sized, empty table
allocateArray(2 * oldArray.length);
occupied = 0;
theSize = 0;
// Copy table over
for (HashEntry<Item> entry : oldArray)
if (entry != null && entry.isActive)
insert(entry.element);
}
/*
* 平方探测解决
*/
private int findPos(Item x) {
int offset = 1;
int currentPos = myhash(x);
while (array[currentPos] != null && !array[currentPos].element.equals(x)) {
currentPos += offset; // Compute ith probe
offset += 2;
if (currentPos >= array.length)
currentPos -= array.length;
}
return currentPos;
}
/*
* 从表中删除
*/
public boolean remove(Item x) {
int currentPos = findPos(x);
if (isActive(currentPos)) {
array[currentPos].isActive = false;
theSize--;
return true;
} else
return false;
}
/*
* 获取当前元素个数
*/
public int size() {
return theSize;
}
/*
* 获取表长度
*/
public int capacity() {
return array.length;
}
/*
* 表中是否包含某元素
*/
public boolean contains(Item x) {
int currentPos = findPos(x);
return isActive(currentPos);
}
/*
* 如果当前位置存在,并可用,返回true
*/
private boolean isActive(int currentPos) {
return array[currentPos] != null && array[currentPos].isActive;
}
/*
* 使hash表为空
*/
public void makeEmpty() {
doClear();
}
private void doClear() {
occupied = 0;
for (int i = 0; i < array.length; i++)
array[i] = null;
}
private int myhash(Item x) {
int hashVal = x.hashCode();
hashVal %= array.length;
if (hashVal < 0)
hashVal += array.length;
return hashVal;
}
/*
* 大于n的素数
*/
private static int nextPrime(int n) {
if (n % 2 == 0)
n++;
while (!isPrime(n))
n += 2;
return n;
}
/*
* 是否素数
*/
private static boolean isPrime(int n) {
if (n == 2 || n == 3)
return true;
if (n == 1 || n % 2 == 0)
return false;
for (int i = 3; i * i <= n; i += 2)
if (n % i == 0)
return false;
return true;
}
// 测试
public static void main(String[] args) {
QuadraticProbingHashTable<String> H = new QuadraticProbingHashTable<>();
long startTime = System.currentTimeMillis();
final int NUMS = 2000000;
final int GAP = 37;
System.out.println("Checking... (no more output means success)");
for (int i = GAP; i != 0; i = (i + GAP) % NUMS)
H.insert("" + i);
for (int i = GAP; i != 0; i = (i + GAP) % NUMS)
if (H.insert("" + i))
System.out.println("OOPS!!! " + i);
for (int i = 1; i < NUMS; i += 2)
H.remove("" + i);
for (int i = 2; i < NUMS; i += 2)
if (!H.contains("" + i))
System.out.println("Find fails " + i);
for (int i = 1; i < NUMS; i += 2) {
if (H.contains("" + i))
System.out.println("OOPS!!! " + i);
}
long endTime = System.currentTimeMillis();
System.out.println("Elapsed time: " + (endTime - startTime));
}
}
标准库中的散列表
标准库中HashSet类和HashMap类是散列表的实现。两类通常使用分离链接散列实现。
小结
- 散列表可以以常数平均时间实现insert和查找操作。
- 当关键字不是短的串或整数时,需要自习选择散列函数
- 分离链接散列法,装填因子尽量接近于1;而探测散列算法,装填因子不应该超过0.5
- 使用散列表不可能找出最小元素。
参考资料
《数据结构与算法分析 Java语言描述》第三版
《算法第四版》