散列表(哈希表)
前言:
本文是学习了学堂在线中,邓俊辉老师讲的《数据结构(下)》课程中的“词典”章节所写
简介:
通过适当的散列函数在词条的关键码与向量单元的秩之间建立起映射关系。
该存储空间称为散列表,映射关系可称为散列函数。
散列表介绍:
将词条的关键码通过映射关系映射到某一存储空间的对应位置。
存储空间是通过映射关系映射之后的地址空间(可称作为桶数组),每个存储单元也称作桶单元。
散列函数:
散列方案(映射关系),可描述为从关键码空间到桶数组地址空间的函数:hash() : key →hash(key)
可以理解为:有N个词条,每个词条设为变量x,散列表A长度为M,合法秩区间为[0,M),映射关系为hash()。词条x存放于桶单元里的A[hash(x)]
装填因子:
即为空间利用率,散列表中非空桶的数目N与桶单元总数M的比值。
好的散列的设计原则:
- 确定性:同意关键码总是被映射至同一地址
- 快速性:保证查询或修改操作的期望执行时间为O(1)
- 满射性:尽可能充分地覆盖整个散列空间
- 均匀性:关键码映射到散列表各位置的概率尽量接近,避免聚集现象
散列函数常用的构造方法:
- 除余法:
,其中M的取值最好为素数,能保证数据对散列表的覆盖最充分,分布最均匀。
缺陷:(1)不动点:无论表长M取值如何,总有hash(0) = 0不满足均匀性。(2)零阶均匀性:相邻词条的散列地址还是相邻
- MAD法:
,M仍需为素数
- 数字分析:抽取key中的某几位构成地址
- 平方取中:取
的中间若干位构成地址
- 折叠法:将key分割成等宽的若干端,取其总和作为地址
- 位异或法:将key分割成等宽的二进制段,经异或运算得到地址
- (伪)随机数法
- 多项式法
- ......等等
越是随机无规律越好,当然方法需要事先规约好
排解冲突的方法:
无论散列函数设计得如何巧妙,都无法保证造成key之间互不冲突,所以需要解决冲突的方法:
- 多槽位法:将冲突的每一组词条组织为一个小规模子词典,分别存放于其共同对应的桶单元中,将每个桶分成更小的槽位,即可容纳多个冲突的词条
缺陷:
1.绝大多数槽位通常处于空闲状态,若桶被分成k个槽位,总共存在N个词条时。装填因子,降低为原先的1/k
2.极端情况下,槽位划分还是会造成溢出
- 独立链法:将多槽位法中的子词典改用列表结构
相对多槽位法可以有效降低空间消耗,查找过程中发生冲突,则需要遍历整个列表,查找成本增加
- 公共溢出区法:在原散列表之外设立一个词典结构D,词条发生冲突就转存至D区域中,D相当于存放冲突词条的公共缓冲池
-
闭散列策略:将散列地址空间对所有词条开放,当词条发生冲突时,按照某规约在散列表内部寻找其他空桶,规约的定制也有很多不同的方法
解决leetcode上第一题两数之和(使用哈希表)
题目:给定一个整数数组 nums
和一个目标值target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
思路:
将数组所有元素与其下标构成散列表,词条就是数组中的元素,选一个词条,target减去该词条所得的结果,也是数组中的元素,即用结果词条去表中查询是否存在,因为需要创建哈希表,空间复杂度为O(n),时间复杂度:创建哈希表O(n),单次遍历O(n),总查询次数O(n),所以总时间复杂度为O(n)。
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
static vector<int> twoSum(vector<int>& nums, int target) {
vector<int> rel;
unordered_map<int, int> hashMap;
for (int i = 0 ; i < nums.size(); ++i)
hashMap[nums[i]] = i;
for (int i = 0; i < nums.size(); ++i)
{
int r = target - nums[i];
if (hashMap.count(r) > 0 && hashMap[r] != i)
{
rel.push_back(i);
rel.push_back(hashMap[r]);
return rel;
}
}
return rel;
}
};
注意,要加上减数的下标与结果的下标需要不同这一条件。