取 s = A * 2w, key *s 为2w位数,前w位为整数部分,后w位为小数部分;
再看上述散列,哈希函数的运算为取s的后w位的前几位为散列值。
![哈希思想的简介、实现与深入_第2张图片](http://img.e-com-net.com/image/info8/53e1491b41bf41e4a306d2290087bf9a.jpg)
3、全域散列法
解释:
为防范恶意存储数据到相同槽,哈希函数被设置为从哈希函数集中随机选取。
Data ==> { hash_f1,hash_f2,hash_f3,…} ===> hashcode
一个常见方法:
取 m 为素数,取随机数 A 为r+1位m进制数,表示为,
将 key 转换为m 进制数,表示为 ,
则哈希函数H[A](k)为:
H[A](k) = sum { Ai * ki, i= 1,..m-1} mod m
结果为桶数组的槽数。
四、冲突处理方法
1、开放寻址法
1)线性检测
以{12,15,4,5}为例
![哈希思想的简介、实现与深入_第3张图片](http://img.e-com-net.com/image/info8/1d792fb872944c37acdc1c627bffe7d0.jpg)
2)二次检测
3)双重散列
hashcode = (h1(key) + i * h2(key)) mod m
2、链地址法
做法是在每一个表格(哈希桶)元素中维护一个list/queue/tree,然后我们在list/queue/tree上进行元素的插入和搜索删除等操作。虽然对list进行搜索是一种线性操作,但是只要list足够短,速度还是很快。
五、哈希函数性能评价
评价标准:
1) 正向快速--高效率、结果分布均匀;
2) 逆向困难;
3) 输入敏感,同时抗干扰强;
4) 冲突避免;
评价角度:
1) 定址---创建时,能够快速有效地确定桶号
e.g 除留余数法的取模运算时间
2) 查找---取值时,能够准确快速地找到内容
e.g 链接法查找时间与表的长度成正比
六、哈希函数的改进
1、取模运算优化
除留余数法是最常用的哈希方法,但取模运算在计算机中的运算效率并不高,所以可以用以下操作代替:
a % b = (b-1) & a, 当b(数组长度)为2的幂时等式成立
JDK中的Hashmap采用的是这样的办法,也因此哈希桶的长度为2的幂。
2、位运算优化
字符串哈希函数(如Time33算法)会遇到大量的数乘运算,可以使用加法和移位代替乘法提升效率:
Hash * 33 = hash + hash << 5
3、扩容
当哈希表容量超过一定程度时,冲突会产生更多,冲突处理降低了哈希表的效率,这时需要对哈希表进行扩容。容量扩张是一次完毕的,期间要花非常长时间一次转移hash表中的全部元素。这样在hash表达到临界时,往里插入一个元素将会等候非常长的时间。
1)扩容多少?
通常为扩容为原来的2倍。当桶数组的长度为2的幂时,意味着不需要重新计算哈希值,而只是判断在原来哈希值的最高位是1还是0.即:
new_hash = 1/0 + old_hash
2)解决扩容带来的时间消耗 ---- 分摊转移
声明一个新的扩容后的哈希表,每当增加一个新元素时,先将旧哈希表一个槽的所有元素转移到新哈希表,再计算该元素位置。
七、哈希思想的实现---MD5
1. 主体描述
1) 输入:512 bits分组
2) 输出:128 bits消息摘要
3) 过程:
a. 消息填充,补齐消息长度为模512时为448 ;
b. 附加消息的长度值,在最后64位加上消息长度,消息总长度达到512的整数倍;
c. 初始化消息摘要(MD)缓存器,128位组;
d. 处理每一组的512位的报文分组
e. 输出
![哈希思想的简介、实现与深入_第4张图片](http://img.e-com-net.com/image/info8/e835c53ae42343eea201205a48a46fc2.jpg)
2. 迭代哈希函数HMD5 -- 思想:单向散列函数 干扰函数(待续)
HMD5由4轮运算组成,每轮16步,分别处理512/32=16个分组,经过64轮处理后,存储初始摘要的位置变为输出摘要。
过程:
(1)对 A 迭代:a ← b + ((a + g(b, c, d) + X[k] + T[i]) <<
(2)缓冲区 (A, B, C, D) 作循环轮换:(B, C, D, A) ← (A, B, C, D)
注:
1)a, b, c, d : MD 缓冲区 (A, B, C, D) 的当前值;
2)g : 轮函数 (F, G, H, I 中的一个);
3)<<
4) X[k] : 32位子明文块;
5)T[i] : 常数;
6)“⊕”为模232操作,基于Davies-Meyer construction理论--- f(x, y) = Ey(x)⊕x
![哈希思想的简介、实现与深入_第5张图片](http://img.e-com-net.com/image/info8/638526dd4b6548bdab4db051dc8e88f3.jpg)
![哈希思想的简介、实现与深入_第6张图片](http://img.e-com-net.com/image/info8/6f46a9c8f9ae420bb5a9cc0757ba9c34.jpg)
八、哈希思想的实现---通用模型(C)
接口文件hash.h
/* 功能:实现一个哈希函数的通用模型
* 作者:waitor
* 日期:20190514
* 描述:使用单链表作为链地址法的数据结构
* 备注:负载因子达到75%时未实现扩容,解决冲突的数据结构可以扩展为红黑树
* */
#include
#include
#include
#include
// #include "myQueue.h"
// 哈希函数
typedef __uint32_t (*hash_code)(void *key);
// 冲突检测函数
typedef int (*compare)(void *key1, void *key2);
typedef struct hash_item hash_item_t;
struct hash_item
{
void *key;
void *value;
hash_item_t *next;
};
typedef struct hash_map hash_map_t;
struct hash_map
{
int size; // 桶大小
int capacity; // 初始容量
float loadFactory; // 负载因子: 所占空间/总大小
void *ths; // 回调类型
hash_code hf; // 回调函数
compare cf; // 回调函数
hash_item_t **map_struct; // 单链表数据结构
};
// 哈希表存储: 0--存储成功; -1 --存储失败,已存在
int hashmap_insert(void *key, hash_map_t *hashmap);
// 哈希表元素删除: 0--删除成功; -1 --删除失败,未找到
int hashmap_delete(void *key, hash_map_t *hashmap);
// 建立并初始化哈希表
hash_map_t *hashmap_new(int size, int capacity);
// 释放哈希表空间
int hashmap_release(hash_map_t *hashmap);
// 设置回调函数
void hashmap_set_hf(hash_map_t *hashmap, void *usr_data, hash_code hf);
// 设置回调函数
void hashmap_set_cf(hash_map_t *hashmap, void *usr_data, compare cf);
具体实现 hash.c
#include "hash.h"
// 新建哈希表
hash_map_t *hashmap_new(int size, int capacity)
{
hash_map_t *hashmap;
hashmap = (hash_map_t *)malloc(sizeof(hashmap));
hashmap->size = size;
hashmap->capacity= capacity;
hashmap->ths = NULL;
hashmap->hf = NULL;
hashmap->cf = NULL;
hashmap->map_struct =(hash_item_t *)malloc(hashmap->size*sizeof(hash_item_t));
return hashmap;
}
// 释放哈希表空间
int hashmap_release(hash_map_t *hashmap)
{
hash_item_t *qn;
hash_item_t *tmp;
for(int i=0; isize; i++)
{
qn = hashmap->map_struct[i];
while(qn) {
tmp = qn->next;
free(qn);
qn = tmp;
}
}
free(hashmap);
return 0;
}
// 设置哈希回调函数
void hashmap_set_hf(hash_map_t *hashmap, void *usr_data, hash_code hf)
{
hashmap->hf = hf;
hashmap->ths = usr_data;
}
// 设置冲突检测回调函数
void hashmap_set_cf(hash_map_t *hashmap, void *usr_data, compare cf)
{
hashmap->cf = cf;
hashmap->ths = usr_data;
}
int hashmap_insert(void *key, hash_map_t *hashmap)
{
__uint32_t hashcode;
hash_item_t *qn;
hashcode = hashmap->hf(key);
qn = hashmap->map_struct[hashcode];
while(qn->next) {
qn = qn->next;
if(hashmap->cf(qn->key, key) == 1) {
printf("已存在\n");
return -1;
}
}
qn->next = (hash_item_t *)((void*)((char*)key-offsetof(hash_item_t,key)));
return 0;
}
int hashmap_delete(void *key, hash_map_t *hashmap)
{
__uint32_t hashcode;
hash_item_t *qn;
hashcode = hashmap->hf(key);
qn = hashmap->map_struct[hashcode];
while(qn->next) {
if(hashmap->cf(qn->next->key, key) == 1) {
hash_item_t *tmp = qn->next->next;
free(qn->next);
qn->next = tmp;
return 0;
}
qn = qn->next;
}
printf("未找到\n");
return -1;
}
测试demo
#include "hash/hash.h"
__uint32_t hashcode(char *str)
{
__uint32_t code = 5381;
while(*str) {
code = (code << 5) + code + *str++;
}
return (code & 0x7FFFFFFF);
}
int compare(char *str1, char *str2)
{
while(*str1)
{
if((*str1++) != (*str2++))
return 0;
}
return 1;
}
int main() {
hash_map_t *hashmap;
hashmap = hashmap_new(10, 100);
hashmap_set_hf(hashmap, NULL, (hashcode)hashcode);
hashmap_set_cf(hashmap, NULL, (compare)compare);
hashmap_release(hashmap);
return 0;
}
参考资料
《算法导论第三版》
《算法笔记》
《Sorting and Searching, Volume 3 of The Art of Compute Programming》;
《Handbook of Algoruthms and Data Structures》;
《Dynamic perfect hashing :Upper and lower bounds 》;
Coron, J.-S., Dodis, Y., Malinaud, C., & Puniya, P. (2005). Merkle-Damgård Revisited: How to Construct a Hash Function.