散列表ADT是一个包含一些项的具有固定大小的数组
散列是一种以常数平均时间O(1)执行插入、删除、查找的技术
JavaCollection中基于散列技术实现了HashSet、HashMap
不支持排序、findMin、findMax等操作
散列表一般是基于HashCode实现散列函数的,故散列表中存储的对象需要hashcode()和equals()方法
1 散列ADT
1.1 基本了解
散列表ADT是一个包含一些项的具有固定大小的数组
查找操作是通过项的某个部分进行的,这个部分叫做关键字
1.2 散列的基本想法
把散列表的大小记作TableSize
把每个关键字映射从0到TableSize-1这个范围的某个数,并放入数组中对应的单元中的技术叫做散列
其中映射到(0 ~ TableSize-1)的函数叫做散列函数(Hash Function)
这个散列函数在一般情况下就是Key mod TableSize,Key一般下就是对象的HashCode()了
1.3 散列的问题
但是散列ADT中存在一个很大的问题就是散列冲突:TableSize必然不能无限大,甚至不能过大,这样必然会有多个关键字映射到同一个值,这样如何存储冲突值就成了一个需要探讨的问题;另外如何能尽可能利用TableSize中的每一个单元,而不是将冲突集中到其中几个单元,剩下的单元利用不充分造成资源浪费
利用散列表需要考虑的问题:
1 如何存储冲突值
2 散列表的大小如何选择
3 散列函数如何计算才能尽可能减小冲突
我们先说明后两个问题:
散列表大小的选择:
散列表的大小选择要求尽可能平均分布,举例若散列表大小为10而关键字都以0为个位,那么所有的关键字都会在0位冲突,其他单元又严重浪费,比较好的办法就是:
保证表的大小是素数(素数:除了1和自己不能被整除)(一般取17以上的素数)
原因:
1 如果关键字是随机的,TableSize是多少其实并无所谓,然而在现实中关键字往往有某种规律,例如大量的等差数列,那么公差和模数不互质的时候发生碰撞的概率会变大,而用质数就可以很大程度上回避这个问题。
2 一般地说, 当模数非常大的时候, 取什么数关系不太大, 质数合数都有不错的结果,但是一般情况下模数不会 “足够大” , 这个时候, “所有” 17以上的素数都有不错的结果, 而很多合数也有不错的结果, 但是个别一些合数结果会非常非常差,冲突非常多.。因此为了稳妥起见, 取17以上的素数.
//是否为素数
private boolean isPrime(int num) {
if (num == 2 || num == 3) {
return true;
}
if (num % 6 != 1 && num % 6 != 5) {
return false;
}
for (int i = 5; i * i <= num; i += 6) {
if (num % i == 0 || num % (i + 2) == 0) {
return false;
}
}
return true;
}
//n后面的下一个素数
private int nextPrime(int n) {
boolean state = isPrime(n);
while (!state) {
state = isPrime(++n);
}
return n;
}
散列函数如何计算
一般情况下的散列函数=对象的Key mod TableSize(对象的Key = 对象的HashCode)
public int myHash(T t) {
int hashValue = t.hashCode() % list.length;
if(hashValue < 0) { //考虑溢出为负数
hashValue += list.length;
}
return hashValue;
}