散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。---来自wiki百科
理想的HashTable的每个关键字都被映射到从0到Hashtable的size-1的这个范围内,并且保证任何两个不同的关键字映射到不同的单元。但是这是不可能的,因为hashtable的尺寸是固定的,而键值可能出现无限个。这就是为什么需要散列函数的原因。
一个好的办法是保证表的大小是素数,因为时常采用mod作为散列函数。
(1)将关键字中的字符的ASCII或Unicode码加起来。这种散列函数计算方便,但是无法很好的分配关键字。例如当关键字只有8个字符长时,则最多可能映射到的数有127*8=1016,但是当我们的hashtable有超过1w+的单元时,则无法充足利用。
(2)第二种散列函数是假设关键字至少有3个字符以上,考察前三个字符,则可以映射到26*26*26=17576种可能,看似能得到均衡的分布,但是实际上我们实际使用的字母之间搭配是有频繁与不频繁之分的,常用的字符3个组合实际只有2851个,实际也没有很好地做到均衡分布。
(3)第三种散列函数,利用horner法则,horner法则也就是秦九韶算法,算法原理如下:(来自wiki百科)
这个散列函数根据Horner法则计算一个多项式函数,为了防止引进负数,需要加上tableSize.。之所以要用Honer法则,可以理解为能使散列函数更加高效。
但是无论散列函数取成啥样,我们都会遇到不同的key映射到同一个单元中的问题,这就是我们常说的哈希冲突。常见的哈希冲突的解决方法有分离链接法和开放定址法。
分离链接法是将散列函数映射到同一个值的所有元素保留到一个数据结构中(常见的主要是二叉树和链表)
import java.util.LinkedList;
import java.util.List;
public class SeparateChainHashTable<AnyType> {
private static final int DEAFAULT_TABLE_SIZE=101;//哈希表的尺寸
private List<AnyType> [] theLists;//哈希表中存储数据的链表数组
private int currentSize;//目前的尺寸可以发生改变
public SeparateChainHashTable() {
this(DEAFAULT_TABLE_SIZE);
}
public SeparateChainHashTable(int size) {
theLists=new LinkedList[nextPrime(size)];
for(int i=0;i<theLists.length;i++) {
theLists[i]=new LinkedList<>();//对每个数组链接的链表进行初始化
}
}
public void insert(AnyType x) {
List<AnyType> whichList = theLists[myhash(x)];//myhash()函数可以返回关键值
if (!whichList.contains(x)) {//这个contains()方法用的是列表类自带的
whichList.add(x);
if (++currentSize > theLists.length) {
rehash();//重新hash
}
}
}
public void revome(AnyType x) {
List<AnyType> whichList = theLists[myhash(x)];
if (whichList.contains(x)) {
whichList.remove(x);
currentSize--;
}
}
public boolean contains(AnyType x) {
List<AnyType> whichList = theLists[myhash(x)];
return whichList.contains(x);
}
public void makeEmpty() {
for (int i = 0; i < theLists.length; i++)
theLists[i].clear();
currentSize = 0;
}
public void rehash() {}
/**
* 该函数的功能是获取x应该放在数组的哪个位置
* @param x
* @return
*/
private int myhash(AnyType x) {
int hashVal=x.hashCode();
hashVal %=theLists.length;
if(hashVal<0) {
hashVal+=theLists.length;
}
return hashVal;
}
private static int nextPrime(int n) {
if (n % 2 == 0)
n++;
for (; !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;
}
}