散列表的实现通常叫做散列hashing。散列是一种用于以常数平均时间执行插入、删除和查找的技术。但是,那些需要元素间任何排序信息的树操作将不会得到有效的支持。理想的散列表数据结构只不过是一个包含一些项的具有固定大小的数组。通常查找是对项的某个部分(数据域)进行的,这部分叫做关键字。例如,项可以由一个串(作为关键字)和其它一些数据域组成。我们把表的大小记作TableSize,并将其理解为散列数据结构的一部分,而不仅仅是浮动于全局的的某个变量。通常习惯是让表从0到TableSize-1变化。
每个关键字被映射到从0到TableSize-1这个范围中的某个数,并且被放到适当的单元中。这个映射就叫做散列函数,理想情况下它应该计算起来简单,并且应该保证任何两个不同的关键字映射到不同的单元。不过,这是不可能的,因为单元的数目是有限的,而关键字实际上是用不完的。因此,我们寻找一个散列函数,该函数要在单元之间均匀地分配关键字。
这就是散列的基本想法,剩下的问题就是要选择一个函数,决定当两个关键字散列到同一个值得时候(即发生冲突)应该做什么以及如何确定散列表的大小。
- 散列函数
如果输入的关键字是整数,则一般合理的方法就是直接返回Key mod TableSize,除非Key碰巧具有某些不合乎需要的性质。在这种情况下,散列函数的选择需要仔细地考虑。例如,若表的大小是10而关键字都以0位个位,则此时上述标准的散列函数就不太好。为了避免上面的情况,好的方法通常是保证表的大小是素数。当输入的关键字是随机整数时,散列函数不仅计算起来简单而且关键字的分配也很均匀。
通常,关键字是字符串,在这种情况下,散列函数需要仔细地选择。剩下的主要编程细节是解决冲突的消除问题。如果当一个元素被插入时与一个已经插入的元素散列到相同的值,那么就产生一个冲突,这个冲突需要消除。解决这种冲突的方法有几种,这里主要介绍分离链接法、开放定址法。
- 分离链接法
分离链状法是将散列到同一个值的所有元素保留到一个表中。我们可以使用标准库表的实现方法。如果空间很紧,则更可取的方法是避免使用它们(因为这些表是双向链接的并且浪费空间)。为执行一次查找,我们使用散列函数来确定究竟遍历哪个链表,然后我们再在相应的链表中查看该元素是否已在适当的位置(如果允许插入重复元,那么通常要留出一个额外的域,这个域当出现匹配事件时增1)。如果这个元素是个新元素,那么它将被插入到链表的前端,这不仅因为方便,还因为常常发生这样的事:新插入的元素最有可能不久又被访问。
我们定义散列表的装填因子 λ为散列表中的元素个数对该表大小的比。执行一次查找所需要的工作是计算散列函数值所需要的常数时间加上遍历链表所用的时间。再一次不成功的查找中,要考察的节点数平均为λ。一次成功的查找则平均需要遍历大约1+(λ/2)个链。分析指出,散列表的大小实际上并不需要,而装填因子才是重要的。分离链状法的一般法则是是的表的大小与预料的元素个数大致相等,即λ≈1。
以下就是一个用分离链接法简单实现的散列表:
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
public class SeparateChainingHashTable<AnyType> {
private static final int DEFAULT_TABLE_SIZE = 10;//默认容量
private List<AnyType>[] theLists;//散列表的数组
private int currentSize;//当前数据个数
public SeparateChainingHashTable() {
this(DEFAULT_TABLE_SIZE);
}
public SeparateChainingHashTable(int size) {
theLists = new LinkedList[nextPrime(size)];
for (int i = 0; i < theLists.length; i++) {
theLists[i] = new LinkedList<AnyType>();
}
}
/**
* 使哈希表变空
*/
public void makeEmpty() {
for (List<AnyType> list : theLists) {
list.clear();
}
currentSize = 0;
}
/**
* 哈希表是否包含某元素
* @param x 查询元素
* @return 查询结果
*/
public boolean contains(AnyType x) {
List<AnyType> whichList = theLists[myhash(x)];
return whichList.contains(x);
}
/**
* 向哈希表中插入某元素,若存在则不操作
* @param x 插入元素
*/
public void insert(AnyType x) {
List<AnyType> whichList = theLists[myhash(x)];
if (!whichList.contains(x)) {
whichList.add(x);
if (++currentSize > theLists.length) {
rehash();
}
} else {
}
}
/**
* 向哈希表中删除某元素,若不存在则不操作
* @param x 删除元素
*/
public void remove(AnyType x) {
List<AnyType> whichList = theLists[myhash(x)];
if (whichList.contains(x)) {
whichList.remove(x);
currentSize--;
} else {
}
}
/**
* 哈希算法,有多种实现方法
* @param x 元素
* @return 哈希值
*/
private int myhash(AnyType x) {
int hashVal = x.hashCode();
hashVal %= theLists.length;
if (hashVal < 0) {
hashVal += theLists.length;
}
return hashVal;
}
/**
* 再散列函数,插入空间不够时执行
*/
private void rehash() {
List<AnyType>[] oldLists = theLists;
// 分配一个两倍大小的空表
theLists = new List[nextPrime(2 * theLists.length)];
for(int j=0;j<theLists.length;j++){
theLists[j]=new LinkedList<AnyType>();
}
currentSize = 0;
for (int i = 0; i < oldLists.length; i++) {
for (AnyType item : oldLists[i]) {
insert(item);
}
}
}
/**
* 检查某整数是否为素数
* @param num 检查整数
* @return 检查结果
*/
private static boolean isPrime(int num) {
if (num == 2 || num == 3) {
return true;
}
if (num == 1 || num % 2 == 0) {
return false;
}
for (int i = 3; i * i <= num; i += 2) {
if (num % i == 0) {
return false;
}
}
return true;
}
/**
* 返回不小于某个整数的素数
* @param num 整数
* @return 下一个素数(可以相等)
*/
private static int nextPrime(int num) {
if (num == 0 || num == 1 || num == 2) {
return 2;
}
if (num % 2 == 0) {
num++;
}
while (!isPrime(num)) {
num += 2;
}
return num;
}
/**
* 输出散列表
*/
public void printTable() {
for(int i=0;i<theLists.length;i++){
System.out.println("-----");
Iterator iterator=theLists[i].iterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+" ");
}
System.out.println();
}
}
public static void main(String[] args) {
Random random = new Random();
SeparateChainingHashTable<Integer> hashTable = new SeparateChainingHashTable<Integer>();
for (int i = 0; i < 30; i++) {
hashTable.insert(random.nextInt(30));
}
hashTable.printTable();
}
}
执行结果:
-----
0
-----
1 24
-----
25 2
-----
3
-----
4 27
-----
5
-----
6 29
-----
7
-----
8
-----
-----
10
-----
-----
-----
13
-----
-----
15
-----
-----
17
-----
18
-----
19
-----
20
-----
21
-----
0
-----
1 24
-----
25 2
-----
3
-----
4 27
-----
5
-----
6 29
-----
7
-----
8
-----
-----
10
-----
-----
-----
13
-----
-----
15
-----
-----
17
-----
18
-----
19
-----
20
-----
21
-----