定义
-
哈希表
(Hash table,也叫散列表
),是根据关键字值
(Key
)直接进行访问的数据结构,他通过把关键字值映射到表中一个位置(数组下标
)来直接访问,以加快查找关键字值的速度。 -
这个映射函数叫做
哈希(散列)函数
,存放记录的数组叫做哈希表
。 -
给定
表M
,存在函数f(key)
,对任意的关键字值key进行映射操作,代入函数后若能得到包含该关键字的表中地址,称表M
为哈希表,函数f(key)
为哈希函数。
最简单的哈希——字符哈希
int main() {
//求字符串s中各个字符出现的次数
string s[] = "sadqweasdqweasdqwe";
//哈希表
int hash_table[26] = {0};
for(int i = 0;i < s.size();i++) {
//哈希转换
int key = s[i] - 'a';
//通过key直接访问、修改value
hash_table[key]++;
}
return 0;
}
遇到以下问题时如何进行哈希转换呢👇
- 负数或者非常大的数,如-5,9999999999999,…
- 字符串,如abcde、XYZ、…
- 浮点数、数组、对象、…
解决
利用哈希函数,将关键字值(key)(大整数、负数、字符串、浮点数、…)转换为整数再对表长取余
,从而关键字值被转换为哈希表表长范围内的整数。
哈希冲突
由于哈希算法被计算的数据是无限的,而计算后的结果范围(哈希表表长)有限,因此总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突。
比如:
77 % 7 = 0,21 % 7 = 0,77和21经过哈希转换后的关键字均为0,此时就发生了冲突。
解决方法
哈希冲突解决方法有:
- 链地址法(拉链法)
- 开放地址法
- 再哈希法
- 创建公共溢出区
Tips:如果使用取余作为哈希转换算法,应尽量设置表长为素数,这样转换出来的key会尽量平均,从根源上减少哈希冲突
这里重点介绍其中的一种——链地址法(拉链法)
将所有哈希函数结果相同的结点链接在同一个单链表中(如下图👇)
若选定的哈希表长度为m,则可将哈希表定义为一个长度为m的指针数组t[0 ,…, m - 1],指针数组中的每个指针指向哈希函数结果相同的单链表。
插入value
将元素value
插入哈希表,若元素的value
的哈希函数值为hashkey
,将value
对应的节点以头插法的方式插入到以t[hashkey]
为头指针的单链表中。
查找value
若元素value
的哈希函数值为hashkey
,遍历以t[hashkey]
为头指针的单链表,遍历查找链表各个节点的值是否为value
。
开放定址法
从发生冲突的那个单元起,按照一定的次序,从哈希表中找到一个空闲的单元。然后把发生冲突的元素存入到该单元的一种方法。开放定址法需要的表长度要大于等于所需要存放的元素。在开放定址法中解决冲突的方法有:线行探查法、平方探查法、双散列函数探查法。开放定址法的缺点在于删除元素的时候不能真的删除,否则会引起查找错误,只能做一个特殊标记。只到有下个元素插入才能真正删除该元素。
再哈希法
就是同时构造多个不同的哈希函数
创建公共溢出区
将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。