数据结构-哈希表原理详解

来源:互联网 发布:snp面膜怎么样知乎 编辑:程序博客网 时间:2024/05/02 03:01

1.什么是哈希表

(摘自百度百科):散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表


2.哈希表原理

(摘自百度百科)给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

基本概念:

(1)若关键字为k,则其值存放在f(k)的存储位置上。由此,不需比较便可直接取得所查记录。称这个对应关系f为散列函数,按这个思想建立的表为散列表
(2)对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为碰撞(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。综上所述,根据散列函数f(k)和处理碰撞的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为散列表,这一映射过程称为散列造表或散列,所得的存储位置称散列地址
(3)若对于关键字集合中的任一个关键字,经散列函数映象到地址集合中任何一个地址的概率是相等的,则称此类散列函数为均匀散列函数(Uniform Hash function),这就是使关键字经过散列函数得到一个“随机的地址”,从而减少碰撞。


几种常见的哈希表设计:

(1)直接寻址表:f(key) = key

这种设计的缺陷:

关键字分布较小范围的时候才会有作用,而且还要求关键字都不能相等,若关键字分布分布广,会浪费大量内存

(2)使用链接法的哈希表设计

我们要定义的表的大小只是关键字的数量,不必关注关键字范围的大小和关键字的重复,这样就不会浪费内存。而且插入一个需要的时间也是O(1),不过我们用Hash表大部分是为了查询,查询的时间复杂度也是O(1)。如何解决碰撞问题呢?对于每一个表槽都给他指针指向一个链表,到时候凡是Hash值相同的映射到同一个槽的都要放到链表里,链表的大小随时变化


此种方式可能的缺陷:若哈希函数的映射不是均匀的,比如恰好h(k1) = h(k2) = h(k3) = .......=h(kn),所有f(k)值都相等,所有关键字都映射到哈希表的一个槽中,记录都串成一个链表,占用的内存比直接的链表结构还多,此时查询的时间复杂度为O(n),得不偿失。所以接下来我们要思考的是:如何设计哈希函数,使得其映射分布尽量均匀

(3)如何设计哈希函数

常见hash函数:

除法哈希:通过关键字除以槽数m将关键字映射到槽里的方法。
哈希函数是H(k)=k Mod m。
举个例子,m=12,k=100,H(100)=4。
而如果m=2k,那么无论k是什么,H(K)的值都是一个0和奇数,也即是说只要奇数槽和0槽被占用,其他的偶数槽都是浪费掉了。如果m=2^r,那么H(k)的值就是k的低r位(化成二进制)。这样造成的后果是某一个槽有很多的关键字。所以来说一般的m取值尽量不要接近2的整数幂,而且还要是质数

缺点:除法在计算机中比较耗时

乘法哈希:用关键字乘A(0<A<1),取其结果的小数再乘以m取整。 Hash函数是H(k)=[m(kA Mod 1)].其优点是对m没有什么要求,一般选择2的整数幂

全域哈希:在向哈希表中插入元素时,如果所有的元素全部被哈希到同一个桶中,此时数据的存储实际上就是一个链表,那么平均的查找时间为 Θ(n) 。而实际上,任何一个特定的哈希函数都有可能出现这种最坏情况,唯一有效的改进方法就是随机地选择哈希函数,使之独立于要存储的元素。这种方法称作全域哈希(Universal Hashing)。

全域哈希的基本思想是在执行开始时,从一组哈希函数中,随机地抽取一个作为要使用的哈希函数。就像在快速排序中一样,随机化保证了没有哪一种输入会始终导致最坏情况的发生。同时,随机化也使得即使是对同一个输入,算法在每一次执行时的情况也都不一样。这样就确保了对于任何输入,算法都具有较好的平均运行情况。


解决碰撞问题:

1.链接法:哈希表中存储指针,指向链表,相同哈希值的记录插入相应链表中

2.开放寻址

线性探测:(方法很简单,略)

二次探测:(方法很简单,略)

二度哈希(也叫双重哈希):有一个包含一组哈希函数 H1...Hn 的集合。当需要从哈希表中添加或获取元素时,首先使用哈希函数 H1。如果导致冲突,则尝试使用 H2,以此类推,直到 Hn。所有的哈希函数都与 H1 十分相似,不同的是它们选用的乘法因子(multiplicative factor)。


先来解析一道需要使用hash表的实际案例:

搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节。假设目前有一千万个记录(这些查询串的重复度比较高,虽然总数是1千万,但如果除去重复后,不超过3百万个。一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。),请你统计最热门的10个查询串,要求使用的内存不能超过1G。


******************************************************************************

完美哈希(重点,面试中经常会被问到)

当关键字的集合是一个不变的静态集合(Static)时,哈希技术还可以用来获取出色的最坏情况性能。如果某一种哈希技术在进行查找时,其最坏情况时间复杂度是O(1) ,则称其为完美哈希(Perfect Hashing)。

完美哈希表的设计:参考我的另一篇博文



3.c++中哈希表接口的使用

头文件#include<hash_map>,并非标准库中的,但绝大部分都实现

参考我的另一篇博文