哈希表

定义

一般的线性表、树,数据在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较“的基础上,查找的效率依赖于查找过程中所进行的比较次数。 若想能直接找到需要的记录,必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应,这就是哈希表。

哈希表又称散列表。哈希表存储的基本思想是:以数据表中的每个记录的关键字 k为自变量,通过一种函数H(k)计算出函数值。把这个值解释为一块连续存储空间(即数组空间)的单元地址(即下标),将该记录存储到这个单元中。在此称该函数H为哈希函数或散列函数。按这种方法建立的表称为哈希表散列表

哈希表是一种数据结构,它可以提供快速的插入操作和查找操作。

哈希表是基于数组结构实现的,所以它也存在一些缺点: 数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重。 这个问题是哈希表不可避免的,即冲突现象:对不同的关键字可能得到同一哈希地址。 所以在以下情况下可以优先考虑使用哈希表: 不需要有序遍历数据,并且可以提前预测数据量的大小

冲突

理想情况下,哈希函数在关键字和地址之间建立了一个一一对应关系,从而使得查找只需一次计算即可完成。由于关键字值的某种随机性,使得这种一一对应关系难以发现或构造。因而可能会出现不同的关键字对应一个存储地址。即k1≠k2,但H(k1)=H(k2),这种现象称为冲突。 把这种具有不同关键字值而具有相同哈希地址的对象称同义词

在大多数情况下,冲突是不能完全避免的。这是因为所有可能的关键字的集合可能比较大,而对应的地址数则可能比较少。

对于哈希技术,主要研究两个问题:

  • (1)如何设计哈希函数以使冲突尽可能少地发生。
  • (2)发生冲突后如何解决。

哈希函数的构造方法

构造好的哈希函数的方法,应能使冲突尽可能地少,因而应具有较好的随机性。这样可使一组关键字的散列地址均匀地分布在整个地址空间。根据关键字的结构和分布的不同,可构造出许多不同的哈希函数。

1).直接定址法

直接定址法是以关键字k本身或关键字加上某个数值常量c作为哈希地址的方法。

该哈希函数H(k)为:

H(k)=k+c (c≥0)

这种哈希函数计算简单,并且不可能有冲突发生。当关键字的分布基本连续时,可使用直接定址法的哈希函数。否则,若关键字分布不连续将造成内存单元的大量浪费

2).除留余数法

取关键字k除以哈希表长度m所得余数作为哈希函数地址的方法。即:

H(k)=k%m

这是一种较简单、也是较常见的构造方法。

这种方法的关键是选择好哈希表的长度m。使得数据集合中的每一个关键字通过该函数转化后映射到哈希表的任意地址上的概率相等。 理论研究表明,在m取值为素数(质数)时,冲突可能性相对较少。

3).平方取中法

取关键字平方后的中间几位作为哈希函数地址(若超出范围时,可再取模)。

设有一组关键字ABC,BCD,CDE,DEF,……其对应的机内码如表所示。假定地址空间的大小为1000,编号为0-999。现按平方取中法构造哈希函数,则可取关键字机内码平方后的中间三位作为存储位置。

4).折叠法

这种方法适合在关键字的位数较多,而地址区间较小的情况。

将关键字分隔成位数相同的几部分。然后将这几部分的叠加和作为哈希地址(若超出范围,可再取模)。

例如,假设关键字为某人身份证号码430104681015355,则可以用4位为一组进行叠加。即有5355+8101+1046+430=14932,舍去高位。 则有H(430104681015355)=4932 为该身份证关键字的哈希函数地址。

5).数值分析法

若事先知道所有可能的关键字的取值时,可通过对这些关键字进行分析,发现其变化规律,构造出相应的哈希函数。

例:对如下一组关键字通过分析可知:

每个关键字从左到右的第l,2,3位和第6位取值较集中,不宜作哈希地址。 剩余的第4,5,7和8位取值较分散,可根据实际需要取其中的若干位作为哈希地址。

6). 随机数法

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即H(key)=random(key),其中random为随机函数。

7). 斐波那契(Fibonacci)散列法

平方散列法的缺点是显而易见的,所以我们能不能找出一个理想的乘数,而不是拿value本身当作乘数呢?答案是肯定的。

  • 1,对于16位整数而言,这个乘数是40503
  • 2,对于32位整数而言,这个乘数是2654435769
  • 3,对于64位整数而言,这个乘数是11400714819323198485

这几个“理想乘数”是如何得出来的呢?这跟一个法则有关,叫黄金分割法则,而描述黄金分割法则的最经典表达式无疑就是著名的斐波那契数列,如果你还有兴趣,就到网上查找一下“斐波那契数列”等关键字,我数学水平有限,不知道怎么描述清楚为什么,另外斐波那契数列的值居然和太阳系八大行星的轨道半径的比例出奇吻合,很神奇,对么?

对我们常见的32位整数而言,公式:

index = (value * 2654435769) >> 28

如果用这种斐波那契散列法的话,那我上面的图就变成这样了:

冲突的解决方法

假设哈希表的地址范围为0~m-l,当对给定的关键字k,由哈希函数H(k)算出的哈希地址为i(0≤i≤m-1)的位置上已存有记录,这种情况就是冲突现象。 处理冲突就是为该关键字的记录找到另一个“空”的哈希地址。即通过一个新的哈希函数得到一个新的哈希地址。如果仍然发生冲突,则再求下一个,依次类推。直至新的哈希地址不再发生冲突为止。

常用的处理冲突的方法有开放地址法、链地址法两大类

1).开放定址法

用开放定址法处理冲突就是当冲突发生时,形成一个地址序列。沿着这个序列逐个探测,直到找出一个“空”的开放地址。将发生冲突的关键字值存放到该地址中去。 如 Hi=(H(k)+d(i)) % m, i=1,2,…k (k 其中H(k)为哈希函数,m为哈希表长,d为增量函数,d(i)=dl,d2…dn-l。

增量序列的取法不同,可得到不同的开放地址处理冲突探测方法。

a)线性探测法

线性探测法是从发生冲突的地址(设为d)开始,依次探查d+l,d+2,…m-1(当达到表尾m-1时,又从0开始探查)等地址,直到找到一个空闲位置来存放冲突处的关键字。

若整个地址都找遍仍无空地址,则产生溢出。

线性探查法的数学递推描述公式为:

d0=H(k)
di=(di-1+1)% m (1≤i≤m-1)

【例】已知哈希表地址区间为0~10,给定关键字序列(20,30,70,15,8,12,18,63,19)。哈希函数为H(k)=k%ll,采用线性探测法处理冲突,则将以上关键字依次存储到哈希表中。试构造出该哈希表,并求出等概率情况下的平均查找长度。

假设数组为A, 本题中各元素的存放过程如下:

H(20)=9,可直接存放到A[9]中去。
H(30)=8,可直接存放到A[8]中去。
H(70)=4,可直接存放到A[4]中去。
H(15)=4,冲突;
d0=4
d1=(4+1)%11=5,将15放入到A[5]中。
H(8)=8,冲突;
d0=8
d1=(8+1)%11=9,仍冲突;
d2=(8+2)%11=10,将8放入到A[10]中。

在等概率情况下成功的平均查找长度为:

(1*5+2+3+4+6)/9 =20/9

利用线性探查法处理冲突容易造成关键字的堆积问题。这是因为当连续n个单元被占用后,再散列到这些单元上的关键字和直接散列到后面一个空闲单元上的关键字都要占用这个空闲单元,致使该空闲单元很容易被占用,从而发生非同义冲突。造成平均查找长度的增加。 为了克服堆积现象的发生,可以用下面的方法替代线性探查法。

b)平方探查法

设发生冲突的地址为d,则平方探查法的探查序列为:d+12,d+22,…直到找到一个空闲位置为止。

平方探查法的数学描述公式为:

d0=H(k)
di=(d0+i2) % m (1≤i≤m-1)

在等概率情况下成功的平均查找长度为:

(1*4+2*2+3+4+6)/9 =21/9

平方探查法是一种较好的处理冲突的方法,可以避免出现堆积问题。它的缺点是不能探查到哈希表上的所有单元,但至少能探查到一半单元。

例如,若表长m=13,假设在第3个位置发生冲突,则后面探查的位置依次为4、7、12、6、2、0,即可以探查到一半单元。

若解决冲突时,探查到一半单元仍找不到一个空闲单元。则表明此哈希表太满,需重新建立哈希表。

2).链地址法

用链地址法解决冲突的方法是: 把所有关键字为同义词的记录存储在一个线性链表中,这个链表称为同义词链表。并将这些链表的表头指针放在数组中(下标从0到m-1)。这类似于图中的邻接表和树中孩子链表的结构。

由于在各链表中的第一个元素的查找长度为l,第二个元素的查找长度为2,依此类推。因此,在等概率情况下成功的平均查找长度为:

(1*5+2*2+3*l+4*1)/9=16/9

虽然链地址法要多费一些存储空间,但是彻底解决了“堆积”问题,大大提高了查找效率。

3). 再哈希法:

Hi=R Hi(key),R和Hi均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。这种方法不易产生聚集,但增加了计算的时间。

4).建立一个公共溢出区

这也是处理冲突的一种方法。

假设哈希函数的值域为[0,m-1],则设向量HashTable[0…m-1]为基本表,每个分量存放一个记录,另设立向量OverTable[0..v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。

哈希表的查找及性能分析

哈希法是利用关键字进行计算后直接求出存储地址的。当哈希函数能得到均匀的地址分布时,不需要进行任何比较就可以直接找到所要查的记录。但实际上不可能完全避免冲突,因此查找时还需要进行探测比较。

在哈希表中,虽然冲突很难避免,但发生冲突的可能性却有大有小。这主要与三个因素有关。

  • 第一:与装填因子有关

所谓装填因子是指哈希表中己存入的元素个数n与哈希表的大小m的比值,即f=n/m。 当f越小时,发生冲突的可能性越小,越大(最大为1)时,发生冲突的可能性就越大。

  • 第二:与所构造的哈希函数有关

若哈希函数选择得当,就可使哈希地址尽可能均匀地分布在哈希地址空间上,从而减少冲突的发生。否则,若哈希函数选择不当,就可能使哈希地址集中于某些区域,从而加大冲突的发生。

  • 第三:与解决冲突的哈希冲突函数有关

哈希冲突函数选择的好坏也将减少或增加发生冲突的可能性。

java 哈希表实现

java中哈希表的实现有多个,比如hashtable,hashmap,currenthashmap,也有其他公司实现的,如apache的FashHashmap,google的mapmarker,high-lib的NonBlockingHashMap,其中差别是:

  • hastable:线程同步,比较慢
  • hashmap:线程不同步,不同步时候读写最快(但是不能保证读到最新数据),加同步修饰的时候, 读写比较慢
  • currenthashmap:线程同步,默认分成16块,写入的时候只锁要写入的快,读取一般不锁块,只有读到空的时候,才锁块,性能比较高,处于hashmap同步和不同步之间。
  • fashhashmap:apache collection 将HashMap封装,读取的时候copy一个新的,写入比较慢(尤其是存入比较多对象每写一次都要复制一个对象,超级慢),读取快
  • NoBlockingHashMap: high_scale_lib实现写入慢,读取较快 MiltigetHashMap,MapMaker google collection,和CurrentHashMap性能相当,功能比较全,可以设置超时,重复的可以保存成list
时间: 2024-11-02 13:53:07

哈希表的相关文章

PHP关联数组与哈希表(hash table) 不指定

PHP中有一种数据类型非常重要,它就是关联数组,又称为哈希表(hash table),是一种非常好用的数据结构. 在程序中,我们可能会遇到需要消重的问题,举一个最简单的模型: 有一份用户名列表,存储了 10000 个用户名,没有重复项: 还有一份黑名单列表,存储了 2000 个用户名,格式与用户名列表相同: 现在需要从用户名列表中删除处在黑名单里的用户名,要求用尽量快的时间处理. 这个问题是一个小规模的处理量,如果实际一点,2 个表都可能很大,比如有 2 亿条记录. 我最开始想到的方法,就是做一

浅谈算法和数据结构 十一 哈希表

在前面的系列文章中,依次介绍了基于无序列表的顺序查找,基于有序数组的二分查找,平衡查找树,以及红黑树,下图是他们在平均以及最差情况下的时间复杂度: 可以看到在时间复杂度上,红黑树在平均情况下插入,查找以及删除上都达到了lgN的时间复杂度. 那么有没有查找效率更高的数据结构呢,答案就是本文接下来要介绍了散列表,也叫哈希表(Hash Table) 什么是哈希表 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值. 哈希的思路很简单

哈希表(Hashtable)

一,哈希表(Hashtable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key/value的键值对,其中key通常可用来快速查找,同时key是区分大小写:value用于存储对应于key的值.Hashtable中key/value键值对均为object类型,所以Hashtable可以支持任何类型的key/value键值对. 二,哈希表的简单操作 在哈希表中添加一个key/value键值对:Hashta

在C#中应用哈希表

      一,哈希表(Hashtable)简述      在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key/value的键值对,其中key通常可用来快速查找,同时key是区分大小写:value用于存储对应于key的值.Hashtable中key/value键值对均为object类型,所以Hashtable可以支持任何类型的key/value键值对.        二,哈希表的简单操作       在哈希表

AS2.0中实现数据结构-哈希表

数据|数据结构 在游戏制作中我们经常需要存储一些离散的对象数据,比如道具箱里的道具,经常需要执行插入和删除操作,而且道具之间没有联系是无序排列的.有些人会说直接用数组不就得了,但是有大量数据存储时的数组的删除插入操作的效率是很低的.因此我们需要哈希表这样的可以提供快速的插入和删除,查找操作的数据结构,不论哈希表中有多少数据,插入和删除操作只需要接近常量的时间:即O(1)的时间级.既然这么好那么我们的AS可以实现吗?当然可以!AS发展到AS2.0,已经成为在语法上更接近于Java + Pascal

数据结构教程 第三十二课 哈希表(一)

教学目的: 掌握哈希表的概念作用及意义,哈希表的构造方法 教学重点: 哈希表的构造方法 教学难点: 哈希表的构造方法 授课内容: 一.哈希表的概念及作用 一般的线性表,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较.这一类查找方法建立在"比较"的基础上,查找的效率依赖于查找过程中所进行的比较次数. 理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键

数据结构-哈希表(C描述)

1.基于分离链解决冲突 1.1主要的存储结构 struct ListNode { ElementType Element; Position Next; }; 这是存储数据的结构,实际上是一个链表. typedef struct ListNode *Position; typedef Position List; struct HashTbl { int TableSize;//哈希表大小 List *TheLists;//指针数组 }; 这是哈希表主结构,*TheLists是一个指针数组,数组

数据结构教程 第三十三课 哈希表(二)

教学目的: 掌握哈希表处理冲突的方法及哈希表的查找算法 教学重点: 哈希表处理冲突的方法 教学难点: 开放定址法 授课内容: 一.复习上次课内容 什么是哈希表?如何构造哈希表? 提出问题:如何处理冲突? 二.处理冲突的方法     成绩一 成绩二... 3 ...     ... ...     24 刘丽 82 95 25 ...     26 陈伟     ... ...     33 吴军     ... ...     42 李秋梅     ... ...     46 刘宏英    

php如何在数组和哈希表上工作

在数组和哈希表上工作 在C语言中, 有两种不同的基础方法用来在一个结构体中存储任意数量的独立数据元素. 两种方法都有赞成者和反对者. 向量 Vs. 链表 应用的编写通常基于特定类型数据的特性的选择, 需要存储多少数据, 以及需要多快速度的检索. 为了能够有对等的认知, 我们先来看看简单的看看这些存储机制. 向量 向量是一块连续的内存空间, 它们包含的数据有规律的间隔. 向量最常见的例子就是字符串变量(char *或char []), 它包含了一个接着一个的字符(字节)序列. char foo[4

C#中用哈希表搜索对象

.NET Framework中的大多数容器都是序列式容器(sequence containers):它们按顺序存储对象.这 种类型的容器功能很多--你可以以任何特殊的顺序来存储任意数量的对象. 然而,这种多功能性是以一定的性能为代价的.在一个序列中查找一个特殊的对象所需要的时间取决 于容器中对象的数量.如果我们没有对容器中元素进行排序,那么随着元素数量的增加,你所需要的查找 时间也就直线增加了:如果容器中元素的数量增加了一倍,那么你用来查找一个特殊元素的时间也就增加 了一倍.然而,如果我们对容器