1. 哈希函数
一般的线性表,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较“的基础上,查找的效率依赖于查找过程中所进行的比较次数。 理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应
而哈希函数就是建立关键字与存储位置的映射关系,使每个关键字能够在内存中有一个确定的位置
2. 哈希表
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度而这个函数就叫做哈希函数
将数据元素的关键字K作为自变量,通过一定的函数关系(称为哈希函数),计算出的值,即为该元素的存储地址
为此在建立一个哈希表之前需要解决两个主要问题:
(1)构造一个合适的哈希函数
均匀性 H(key)的值均匀分布在哈希表中;
简单以提高地址计算的速度
常用的哈希函数采用的方法有:
① 保留余数法
取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词
static int hash(Object x, int prime){
return x.hashCode() % prime;
}
Java中每个对象都有自己的HashCode那么得到自己的HashCode之后再取散列表的长度的余数,那么可以确保所有的元素都会落在散列表中
② 加法
首先得到对象的字符串长度然后再循环中使用这个长度每一次累加字符的ascii值,最后取散列表的长度的余数
static int additive(Object key, int prime){
String objStr = key.toString();
int hash = objStr.length();
System.out.println(hash);
for(int i = 0; i < objStr.length(); i++){
hash += objStr.charAt(i);
System.out.println(hash + " " + objStr.charAt(i) + " " + (int)objStr.charAt(i));
}
return (hash % prime);
}
}
③ 旋转哈希的方法
static int rotatingHash(String key, int prime){
int hash = key.length();
for(int i = 0; i < key.length(); i++){
hash = (hash << 4) ^ (hash >> 28) ^ key.charAt(i);
}
return (hash % prime);
}
使用的是位运算来进行操作
④ 乘法
static long bernStein(String key, int prime){
long h = 0;
long seed = 31; //一般为素数
for(int i = 0; i < key.length(); i++){
h = seed * h + key.charAt(i);
}
return h % prime;
}
比较常用的是以上几种,还有一些其他的方法比如直接定址法,数字分析法,平方取中法,折叠法,随机数法这些
(2)冲突的处理
冲突:在哈希表中,不同的关键字值对应到同一个存储位置的现象,即关键字K1≠K2,但H(K1)= H(K2)。均匀的哈希函数可以减少冲突,但不能避免冲突。发生冲突后,必须解决;也即必须寻找下一个可用地址
解决冲突的方法主要有:开放定址法,拉链法,双散列,单散列,其中比较常用的是拉链法
拉链法采用的是链表的方式来进行解决,因为有可能若干个元素是落在同一个桶中的,那么在在桶中我们可以采用链表的形式来链接桶中的各个元素来解决冲突的存在
3. 尽量使用较好的哈希函数因为这样我们计算出来的哈希值才能在最大程度上保持均匀分布,数据才不会集中到一个桶中,假如数据均匀分布在各个桶中那么在查询的时候会大大加快我们的查询速度