哈希表支持一种最有效的检索方法:散列。

从根来上说,一个哈希表包含一个数组,通过特殊的索引值(键)来访问数组中的元素。

哈希表的主要思想是通过一个哈希函数,在所有可能的键与槽位之间建立一张映射表。哈希函数每次接受一个键将返回与键相对应的哈希编码或哈希值。键的数据类型可能多种多样,但哈希值的类型只能是整型。

计算哈希值和在数组中进行索引都只消耗固定的时间,因此哈希表的最大亮点在于它是一种运行时间在常量级的检索方法。当哈希函数能够保证不同的键生成的哈希值互不相同时,就说哈希表能直接寻址想要的结果。但这只是理想状态,在实际运用过程中,能够直接寻址结果的情况非常少。

通常与各种各样的键相比,哈希表的条目数相应较少。因此,绝大多数的哈希函数会将一些不同的键映射到表中相同的槽位上。当两个键映射到一个相同的槽位上时,它们就产生了冲突。一个好的哈希函数能最大限度的减少冲突,但冲突不能完全消除,我们仍要想办法处理这些冲突。

链式哈希表的描述

链式哈希表从根本上说是由一组链表构成。每个链表都可以看做是一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中。插入元素时,首先将其键传入一个哈希函数(该过程称为哈希键),函数通过散列的方式告知元素属于哪个“桶”,然后在相应的链表头插入元素。查找或删除元素时,用同们的方式先找到元素的“桶”,然后遍历相应的链表,直到发现我们想要的元素。因为每个“桶”都是一个链表,所以链式哈希表并不限制包含元素的个数。然而,如果表变得太大,它的性能将会降低。

解决冲突

当哈希表中两个键散列到一个相同的槽位时,这两个键之间将会产生冲突。链式哈希表解决冲突的方法非常简单:当冲突发生时,它就将元素放到已经准备好的“桶”中。但这同样会带来一个问题,当过多的冲突发生在同一槽位时,此位置的“桶”将会变得越来越深,从而造成访问这个位置的元素所需要的时间越来越多。

在理想情况下,我们希望所有的“桶"以几乎同样的速度增长,这样它们就可以尽可能的保持小的容量和相同的大小。换句话说,我们的目标就是尽可能的均匀和随机地分配表中的元素,这种情况在理论上称为 均匀散列,而在实际中,我们只能尽可能近似达到这种状态。

如果想插入表中的元素数量远大于表中的“桶‘的数量,那么即使是在一个均匀散列的过程中,表的性能也会迅速下降。在这种情况下,表中所有的”桶“都变得越来越深。因此,我们必须要特别注意一个哈希表的负载因子,其定义为:

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)_哈希概述的更多相关文章

  1. 算法与数据结构基础 - 哈希表(Hash Table)

    Hash Table基础 哈希表(Hash Table)是常用的数据结构,其运用哈希函数(hash function)实现映射,内部使用开放定址.拉链法等方式解决哈希冲突,使得读写时间复杂度平均为O( ...

  2. PHP关联数组和哈希表(hash table) 未指定

    PHP有数据的一个非常重要的一类,就是关联数组.又称为哈希表(hash table),是一种很好用的数据结构. 在程序中.我们可能会遇到须要消重的问题,举一个最简单的模型: 有一份username列表 ...

  3. 什么叫哈希表(Hash Table)

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

  4. 词典(二) 哈希表(Hash table)

    散列表(hashtable)是一种高效的词典结构,可以在期望的常数时间内实现对词典的所有接口的操作.散列完全摒弃了关键码有序的条件,所以可以突破CBA式算法的复杂度界限. 散列表 逻辑上,有一系列可以 ...

  5. 哈希表(Hash table)

  6. java哈希表(线性探测哈希表。链式哈希表)

    哈希表(散列表) 通过哈希函数使元素的存储位置与它 的关键码之间能够建立一一映射的关系,在查找时可以很快找到该元素. 哈希表hash table(key,value) 的做法其实很简单,就是把Key通 ...

  7. Redis原理再学习04:数据结构-哈希表hash表(dict字典)

    哈希函数简介 哈希函数(hash function),又叫散列函数,哈希算法.散列函数把数据"压缩"成摘要,有的也叫"指纹",它使数据量变小且数据格式大小也固定 ...

  8. Hash表 hash table 又名散列表

    直接进去主题好了. 什么是哈希表? 哈希表(Hash table,也叫散列表),是根据key而直接进行访问的数据结构.也就是说,它通过把key映射到表中一个位置来访问记录,以加快查找的速度.这个映射函 ...

  9. 深入理解PHP内核(六)哈希表以及PHP的哈希表实现

    原文链接:http://www.orlion.ga/241/ 一.哈希表(HashTable) 大部分动态语言的实现中都使用了哈希表,哈希表是一种通过哈希函数,将特定的键映射到特定值得一种数据 结构, ...

随机推荐

  1. SSM框架开发web项目系列(五) Spring集成MyBatis

    前言 在前面的MyBatis部分内容中,我们已经可以独立的基于MyBatis构建一个数据库访问层应用,但是在实际的项目开发中,我们的程序不会这么简单,层次也更加复杂,除了这里说到的持久层,还有业务逻辑 ...

  2. swift中的AnyHashable

    AnyHashable是调和objc和Swift的产物 典型的场景是,objc下无泛型的NSDictionary到了Swift下,会变成什么?(典型例子是苹果来的推送消息) [Any: Any],这肯 ...

  3. [PHP] 编译构建最新版PHP源码

    获取最新PHP代码git clone https://git.php.net/repository/php-src.git构建编译环境apt-get install build-essential 编 ...

  4. 一次Oracle宕机切换后产生ORA错误的处理过程

    问题背景 机房意外断电后Oracle主服务器启动失败,Oracle备机接管 为了安全,管理员对于数据库做expdp的逻辑备份.但备份时发现AttributeInstance表备份失败,提示ORA-01 ...

  5. JaveScript数组(JS知识点归纳五)

    1.概念 a)作用--用于保存多个数据,便于对数据的使用 b)数组元素--数组中的数据 c)索引--数组中的元素按照下标(数值)的方式排列(从0 开始),依次递增(也有可能是字符串的类型--不用) d ...

  6. Vijos P1116 一元三次方程求解【多解,暴力,二分】

    一元三次方程求解 描述 有形如:ax^3+bx^2+cx+d=0 这样的一个一元三次方程.给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之 ...

  7. hdu_1015(dfs)

    题意:根据给出的计算公式,给一个n和一个字符集,问能不能在字符串集中找到不重复的五个字符,让其计算结果等于给定的n,如果有多个解输出字典序最大的一个 题解:dfs直接上代码了 code: #inclu ...

  8. 最短路(spfa)

    http://acm.hdu.edu.cn/showproblem.php?pid=2544 最短路 Time Limit: 5000/1000 MS (Java/Others)    Memory ...

  9. 自己编写JavaScript的sort函数

    在平常开发中我们经常会遇到对数组进行排序的场景,js给我们提供了sort方法可以对数组元素进行排序,默认是按ASCII字母表顺序排序,请看下面例子: var a = [1, 3, 2, 4];var ...

  10. ZOJ 1203 Swordfish

    题目: There exists a world within our world A world beneath what we call cyberspace. A world protected ...