数据结构 哈希表(Hash Table)_哈希概述
哈希表支持一种最有效的检索方法:散列。
从根来上说,一个哈希表包含一个数组,通过特殊的索引值(键)来访问数组中的元素。
哈希表的主要思想是通过一个哈希函数,在所有可能的键与槽位之间建立一张映射表。哈希函数每次接受一个键将返回与键相对应的哈希编码或哈希值。键的数据类型可能多种多样,但哈希值的类型只能是整型。
计算哈希值和在数组中进行索引都只消耗固定的时间,因此哈希表的最大亮点在于它是一种运行时间在常量级的检索方法。当哈希函数能够保证不同的键生成的哈希值互不相同时,就说哈希表能直接寻址想要的结果。但这只是理想状态,在实际运用过程中,能够直接寻址结果的情况非常少。
通常与各种各样的键相比,哈希表的条目数相应较少。因此,绝大多数的哈希函数会将一些不同的键映射到表中相同的槽位上。当两个键映射到一个相同的槽位上时,它们就产生了冲突。一个好的哈希函数能最大限度的减少冲突,但冲突不能完全消除,我们仍要想办法处理这些冲突。
链式哈希表的描述
链式哈希表从根本上说是由一组链表构成。每个链表都可以看做是一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中。插入元素时,首先将其键传入一个哈希函数(该过程称为哈希键),函数通过散列的方式告知元素属于哪个“桶”,然后在相应的链表头插入元素。查找或删除元素时,用同们的方式先找到元素的“桶”,然后遍历相应的链表,直到发现我们想要的元素。因为每个“桶”都是一个链表,所以链式哈希表并不限制包含元素的个数。然而,如果表变得太大,它的性能将会降低。

解决冲突
当哈希表中两个键散列到一个相同的槽位时,这两个键之间将会产生冲突。链式哈希表解决冲突的方法非常简单:当冲突发生时,它就将元素放到已经准备好的“桶”中。但这同样会带来一个问题,当过多的冲突发生在同一槽位时,此位置的“桶”将会变得越来越深,从而造成访问这个位置的元素所需要的时间越来越多。
在理想情况下,我们希望所有的“桶"以几乎同样的速度增长,这样它们就可以尽可能的保持小的容量和相同的大小。换句话说,我们的目标就是尽可能的均匀和随机地分配表中的元素,这种情况在理论上称为 均匀散列,而在实际中,我们只能尽可能近似达到这种状态。
如果想插入表中的元素数量远大于表中的“桶‘的数量,那么即使是在一个均匀散列的过程中,表的性能也会迅速下降。在这种情况下,表中所有的”桶“都变得越来越深。因此,我们必须要特别注意一个哈希表的负载因子,其定义为:
a=n / m
其中n是表中的元素的个数,m是桶的个数。在均匀散列的情况下,链式哈希表的负载因子告诉我们表中的”桶“能装下元素个数的最大值。
例如,有一个链式哈希表,其”桶“的数量是m=1699,元素的数量n=3198,其负载因子a=3198/1699=2。所以在这种情况下,当查找元素时,可能每个”桶“里面的元素个数不超过两个。当有一个表的负载因子小于1时,这个表每个位置所包含的元素不超过1个。当然,由于均匀散列是一个理想的近似的情况,因此在实际情况中我们往往会检索超过负载因子建议的数值。如何达到更接近于均匀散列的情况,最终取决于如何选择哈希函数。
选择哈希函数
一个好的哈希函数旨在均匀散列,也就是,尽可能以均匀和随机的方式散布一个哈希表中的元素。定义一个哈希函数,它将键k映射到哈希表中的位置x。x称为k的哈希编码,正式的表述为:
h(k) = x
一般来说,大多数的散列方法都假设k为整数,这样k能够很容易地以数学方式修改,从而使得h能够更均匀地将元素分布在表中。当k不是一个整数时,我们也可以很容易的将它强制置转换为整型。
如何强制转换一组键,很大程度上取决于键本身的特点。所以,在一个特定的应用中,尽可能地获取键的特性尤为重要。例如,如果我们想对程序中的标识符进行散列,会发现程序中有很多相似的前缀和后缀,因为开发人员倾向于将变量声明为类似sampleptr、simpleptr和sentryptr的名字。我们可以将键严格按照键的开头和结尾字符来强制转换,但这显然不是一个好办法,因为对于一个k会有多个整数与之对应。另一方面,我们不妨随机地从4个位置来选择字符,然后随机地改变它们的顺序,并将它们封装到一个4字节的整数中。要记住,无论用什么样的方法来强制转换键,目的都是尽可能选择一个能将键均匀、随机地分布到表中的哈希函数。
取余法
一种简单地将整型k映射到m槽位的散列方法是计算k除以m所得到的余数。我们称之为取余法,正式的表述为:
h(k) = k mod m
如果有m=1699个位置,而要散列的键k = 25657,通过这种方法得到哈希编码为25657 mod 1699 = 172。通常情况下,要避免m的值为2的幂。这是因为假如m=2p ,那么h仅仅是k的p个最低阶位。通常我们选择的m会是一个素数,且不要太接近于2的幂,同时还要考虑存储空间的限制和负载因子。
例如,如果我们想往一个链式哈希表中插入n=4500个左右的元素,会选择m=1699(m是一个介于210~211之间的素数)。由此可以计算出它的负载因子a=4500/1699约等于2.6,根据均匀散列表述,这说明表中每个“桶”大概能容纳2~3个元素。
乘法
与取余法不同的是乘法。它将整型k乘以一个常数A(0<A<1);取结果的小数部分;然后再乘以m取结果的整数部分。通常情况下,A取0.618,它由5的开平方减1再除以2得到。这个方法称为乘法,正式的表述为:
h(k) = m(kA mod 1), 其中A约等于0.618
这个方法有个优点是,对于表中槽位个数m的选择并不需要像取余法中那么慎重。例如:如果表有m=2000个位置,散列的键k=6341,那么得到的哈希编码为2000X(6341X0.618 mod 1) = 2000X(3918.738 mod 1)=2000X0.738=1476。
在链式哈希表中,如果期待插入的元素个数n不超过4500个,可以让m=2250。这样得到的负载因子a=4500/2250=2,根据均匀散列的规则,在每个“桶”中存储的元素个数一般不超过两个。同时,这个散列方法可以让我们更加灵活地选择m,以便获取我们可以接受的最大“桶”深。
示例1列举了一个能够较好的处理字符串的哈希函数。它通过一系列的位操作将键强制转换为整数。所有这些整数都是通过取余法得到的。这个哈希函数针对哈希字符串执行的很好。
示例1:一个适用于处理字符串的哈希函数
/*hashpjw.c*/
#include "hashpjw.h" unsigned int hashpjw(const void *key)
{
const char *ptr;
unsigned int val;
/*通过一系列的位操作,将键强制转换为整数*/
val=;
ptr=key; while(*ptr != '\0')
{
unsigned int tmp;
val = (val << ) + (*ptr); if(tmp = (val & oxf0000000))
{
val = val ^ (tmp >> );
val = val ^ tmp;
} ptr++;
}
/*在实际操作中,使用实际大小代替PRIME_TBLSIZ*/
return val % PRIME_TBLSIZ;
}
另外一篇文章中,我们将详细讨论链式哈希表的接口定义与实现分析。
数据结构 哈希表(Hash Table)_哈希概述的更多相关文章
- 算法与数据结构基础 - 哈希表(Hash Table)
Hash Table基础 哈希表(Hash Table)是常用的数据结构,其运用哈希函数(hash function)实现映射,内部使用开放定址.拉链法等方式解决哈希冲突,使得读写时间复杂度平均为O( ...
- PHP关联数组和哈希表(hash table) 未指定
PHP有数据的一个非常重要的一类,就是关联数组.又称为哈希表(hash table),是一种很好用的数据结构. 在程序中.我们可能会遇到须要消重的问题,举一个最简单的模型: 有一份username列表 ...
- 什么叫哈希表(Hash Table)
散列表(也叫哈希表),是根据关键码值直接进行访问的数据结构,也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做散列函数,存放记录的数组叫做散列表. - 数据结构 ...
- 词典(二) 哈希表(Hash table)
散列表(hashtable)是一种高效的词典结构,可以在期望的常数时间内实现对词典的所有接口的操作.散列完全摒弃了关键码有序的条件,所以可以突破CBA式算法的复杂度界限. 散列表 逻辑上,有一系列可以 ...
- 哈希表(Hash table)
- java哈希表(线性探测哈希表。链式哈希表)
哈希表(散列表) 通过哈希函数使元素的存储位置与它 的关键码之间能够建立一一映射的关系,在查找时可以很快找到该元素. 哈希表hash table(key,value) 的做法其实很简单,就是把Key通 ...
- Redis原理再学习04:数据结构-哈希表hash表(dict字典)
哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...
- Hash表 hash table 又名散列表
直接进去主题好了. 什么是哈希表? 哈希表(Hash table,也叫散列表),是根据key而直接进行访问的数据结构.也就是说,它通过把key映射到表中一个位置来访问记录,以加快查找的速度.这个映射函 ...
- 深入理解PHP内核(六)哈希表以及PHP的哈希表实现
原文链接:http://www.orlion.ga/241/ 一.哈希表(HashTable) 大部分动态语言的实现中都使用了哈希表,哈希表是一种通过哈希函数,将特定的键映射到特定值得一种数据 结构, ...
随机推荐
- [SharePoint Online]SharePoint Designer无法打开世纪互联版sp online站点得解决方法,报错信息:请安装更新后再重新打开
现象描述: 装了个x64版SharePoint designer 2013, 没有装SP1,在打开国际版得office 365 online得时候完全没有问题,但是在打开世纪互联版得时候就打不开,让安 ...
- PyQt4中的Treeview
import sys from PyQt4 import QtCore, QtGui from qyolk import Ui_QYolk from yolk import yolklib class ...
- Kylin与CDH兼容性剖析
1. 概述 Apache Kylin™是一个开源的分布式分析引擎,提供Hadoop之上的SQL查询接口及多维分析(OLAP)能力以支持超大规模数据,最初由eBay Inc. 开发并贡献至开源社区.它能 ...
- 使用docker+jenkins构建nodejs前端项目
前文使用Docker搭建Jenkins+Docker持续集成环境我们已经搭建了基于docker+jenkins的持续集成环境,并构建了基于maven的项目.这一节,我们继续扩展功能,增加对Nodejs ...
- 博客已经迁移到 http://imbotao.top 也会同步到这儿
完全是看到别人搭建的 hexo + github Pages 博客界面很好看,很简洁,自己也喜欢折腾,就鼓捣了一个. 也在阿里云买了自己的域名,个人感觉在博客的样式和功能上花费了太多的时间,不过现在终 ...
- Python全栈考试(一)
1.执行 Python 脚本的两种方式 1) 使用命令行执行Python脚本: python C:\tesst.py 2) 使用各种IDE软件.pycharm wing 2.简述位.字节的关系 位 ( ...
- java_web学习(六) request对象中的get和post差异
1.get与post的区别 Get和Post方法都是对服务器的请求方式,只是他们传输表单的方式不一样. 下面我们就以传输一个表单的数据为例,来分析get与Post的区别 1.1 get方法 jsp中 ...
- JS高级代码
JS 的defineProperties 设置多个属性 var book = {}; //用Object.defineProperties()方法设置多个属性 Object.definePropert ...
- POJ 1804 Brainman(5种解法,好题,【暴力】,【归并排序】,【线段树单点更新】,【树状数组】,【平衡树】)
Brainman Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 10575 Accepted: 5489 Descrip ...
- 图解vue生命周期
学习vue时搞清楚生命周期可以帮助你知道什么时候在什么地方执行该执行的方法,话不多说上图: 复制下面代码可以在控制台更详细展示各个钩子的状态 <!DOCTYPE html> <htm ...