散列:
散列是一种用于以常数平均时间执行插入、删除、查找的技术。但是那些需要元素间任何
排序信息的树操作将不会得到有效的支持。因此,诸如findMin、findMax以及线性时间将排过序的
整个表进行打印的操作都是散列表所不支持的。
一般想法:
理想的散列表数据结构只不过是一个包含一些项(item)的具有固定大小
的数组。通常查找是对项的某个部分(数据域)进行的。这个部分就叫做关键字(key)。例如,项
可以由一个串(它可以作为关键字)和其他一些数据域(例如:姓名是大型雇员结构的一部分)。我们把
表的大小记作TableSize,并将其理解为散列数据结构的一部分,而不仅仅是浮动与全局的某个变量。通常的习惯
是让表从0到tableSize - 1变化。
下标 | Tables |
---|---|
0 | |
1 | |
2 | |
3 | john 25000 |
4 | phil 31250 |
5 | |
6 | dave 27500 |
7 | mary 28200 |
8 | |
9 |
每个关键字被映射到0到tableSize - 1这个范围中的某个数,并且放到适当
的单元中。这个映射就叫做散列函数(hash function),理想情况下他应该计算起来简单,并且应该
保证任何两个不同的元素映射到不同的单元。不过这是不可能的,因为表格的单元数量有限的,而关键字是
用不完的。因此,我们要寻找一个散列函数, 该函数在表中均匀的分配关键字。上面一个是完美情况的一个
典型,john散列到了3, phil散列到了4, dave散列到了6,mary散列到了7。
这就是散列的基本思想。剩下的问题就是要选择一个函数决定当两个关键字
散列到同一个值得时候(这就叫做冲突(collision))应该做什么以及如何确定散列表的大小问题。
散列函数
如果输入的关键字是整数,则一般合理的方法就是直接返回Key mod Tablesize,除非Key碰巧据有某些不合乎需要的性质。在这种情况下, 散列函数的选择需要仔细的考虑。最好的方法就是保证表的大小是素数。
通常一般来说, 关键字是字符串;在这种情形下,散列函数需要仔细的选择。
1.第一种策略就是把字符串中的ASCLL(或Unicode码)值加起来。
public static int hash(String key, int tableSize){
int hashVal = 0;
for(int i = 0; i < key.length(); i++)
hashVal += key.charAt(i);
return hashVal % tableSize;
}
这个散列函数实现起来简单而且能够很快地计算出答案。不过如果表很大函数将不会很好的分配关键字。
例如,设TableSize = 10007(10007是素数),并且设所有的关键字的至多8个字符。ASCLL码最多127,
因此散列函数只能假设值在0和1016之间,其中1016为127 * 8.显然这不是一种均匀的分配。
2.第二种值27表示英文字母外加一个空格, 但是这种设计是不是非常的合理的,Key至少有3个字符, 假如它的值是10007, 那么其实2的立方是17576种组合, 但是实际上只有2851
public static int hash(String key, int tableSize){
return (key.charAt(0) + 27 * key.chatAt(1) + 729
* key.charAt(2)) % tableSize;
}
3.利用Horner法则计算一个(37的)多项式函数。这个散列函数利用到事实:允许溢出。这可能会引进负的数, 所以在末尾有附加的测试。加入关键字特别长那么就有可能会在散列过多的时间。这种是有多种解决的方法的, 其中一种就是利用只是在奇数上的位置上的字符来实现他们的散列函数这里有这么一层想法:用计算散列函数节省下的时间来补偿由此产生对均匀地分布的函数轻微干扰。
public static int hash(String key, int tableSize){
int hashVal = 0;
for(int i = 0; i < key.length(); i++)
hashVal = 37 * hashVal + key.charAt(i);
hashVal %= tableSize;
if(hashVal < 0)
hashVal += tableSize;
return hashVal;
}