哈希表的理解

哈希表是种数据结构,它可以提供快速的插入操作和查找操作。第一次接触哈希表时,它的优点多得让人难以置信。不论哈希表中有多少数据,插入和删除(有时包括侧除)只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。

  对哈希表的使用者一一人来说,这是一瞬间的事。哈希表运算得非常快,在计算机程序中,如果需要在一秒种内查找上千条记录通常使用哈希表(例如拼写检查器)哈希表的速度明显比树快,树的操作通常需要O(N)的时间级。哈希表不仅速度快,编程实现也相对容易。

哈希表也有一些缺点它是基于数组的,数组创建后难于扩展某些哈希表被基本填满时,性能下降得非常严重,所以程序虽必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。

如果不需要有序遍历数据,井且可以提前预测数据量的大小。那么哈希表在速度和易用性方面是无与伦比的。

哈希表算法-哈希表的概念及作用

哈希表简单的理解:在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。

举例:

哈希表最常见的例子是以学生学号为关键字的成绩表,1号学生的记录位置在第一条,10号学生的记录位置在第10条...

如果我们以学生姓名为关键字,如何建立查找表,使得根据姓名可以直接找到相应记录呢?

上面这张表即哈希表。

如果将来要查李秋梅的成绩,可以用上述方法求出该记录所在位置:

李秋梅:lqm 12+17+13=42 取表中第42条记录即可。

问题:如果两个同学分别叫 刘丽 刘兰 该如何处理这两条记录?

这个问题是哈希表不可避免的,即冲突现象:对不同的关键字可能得到同一哈希地址。

哈希表算法-处理冲突的方法

如果两个同学分别叫 刘丽 刘兰,当加入刘兰时,地址24发生了冲突,我们可以以某种规律使用其它的存储位置,如果选择的一个其它位置仍有冲突,则再选下一个,直到找到没有冲突的位置。选择其它位置的方法有:

1、开放定址法

Hi=(H(key)+di) MOD m i=1,2,...,k(k<=m-1)

其中m为表长,di为增量序列

如果di值可能为1,2,3,...m-1,称线性探测再散列。

如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,...k*k,-k*k(k<=m/2)

称二次探测再散列

如果di取值可能为伪随机数列。称伪随机探测再散列。

比如有一组关键字{12,13,25,23,38,34,6,84,91},Hash表长为11,Hash函数为address(key)=key%11,当插入12(hash(12)=1),13(hash(13)=2),25(hash(25)=3)时可以直接插入,而当插入23时,地址1被占用了,因此沿着地址1依次往下探测(探测步长可以根据情况而定,如(hash(23)+1)%11=2,(hash(23)+2)%11=3,(hash(23)+3)%11=4),直到探测到地址4,发现为空,则将23插入其中。

2、再哈希法

当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。

3、链地址法

将所有关键字为同义词的记录存储在同一线性链表中。

虽然能够采用一些办法去减少冲突,但是冲突是无法完全避免的。因此需要根据实际情况选取解决冲突的办法。

哈希表算法-哈希表的构造方法

1、直接定址法

例如:有一个从1到100岁的人口数字统计表,其中,年龄作为关键字,哈希函数取关键字自身。

但这种方法效率不高,时间复杂度是O(1),空间复杂度是O(n),n是关键字的个数

2、数字分析法

有学生的生日数据如下:

年.月.日

75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...

经分析,第一位,第二位,第三位重复的可能性大,取这三位造成冲突的机会增加,所以尽量不取前三位,取后三位比较好。

3.平方取中法

  对关键字进行平方运算,然后取结果的中间几位作为Hash地址。假如有以下关键字序列{421,423,436},平方之后的结果为{177241,178929,190096},那么可以取{72,89,00}作为Hash地址。

4、折叠法

将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。

例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字,若要以它作关键字建立一个哈希表,当馆藏书种类不到10,000时,可采用此法构造一个四位数的哈希函数。如果一本书的编号为0-442-20586-4,则:

5.除留取余法

  如果知道Hash表的最大长度为m,可以取不大于m的最大质数p,然后对关键字进行取余运算,address(key)=key%p。

  在这里p的选取非常关键,p选择的好的话,能够最大程度地减少冲突,p一般取不大于m的最大质数。

6、随机数法

选择一个随机函数,取关键字的随机函数值为它的哈希地址,即

H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法。

Hash表大小的确定

  Hash表大小的确定也非常关键,如果Hash表的空间远远大于最后实际存储的记录个数,则造成了很大的空间浪费,如果选取小了的话,则容易造成冲突。在实际情况中,一般需要根据最终记录存储个数和关键字的分布特点来确定Hash表的大小。还有一种情况时可能事先不知道最终需要存储的记录个数,则需要动态维护Hash表的容量,此时可能需要重新计算Hash地址。

Hash表的平均查找出长度

Question1:

将关键字序列(7、8、30、11、18、9、14)散列存储到散列表中。散列表的存储空间是一个下标从0开始的一维数组,散列函数为:      H(key) = (keyx3) MOD 7,处理冲突采用线性探测再散列法,要求装填(载)因子为0.7。

(1) 请画出所构造的散列表。

(2) 分别计算等概率情况下查找成功和查找不成功的平均查找长度。

Ans:

(1).首先明确一个概念装载因子,装载因子是指所有关键子填充哈希表后饱和的程度,它等于 关键字总数/哈希表的长度。 根据题意,我们可以确定哈希表的长度为 L = 7/0.7 = 10;因此此题需要构建的哈希表是下标为0~9的一维数组。根据散列函数可以得到如下散列函数值表。

H(Key) = (keyx3) MOD 7, 例如key=7时, H(7) = (7x3)%7 = 21%7=0,其他关键字同理。

采用线性探测再散列法处理冲突,所构造的散列表为:

下面对散列表的构造方式加以说明,注意表1中的关键字7和14,30和9, 11和18,这三组关键子的H(Key)值相同,这在构建散列表时就会产生冲突,因为他们的地址相同,所以要通过一定的冲突处理方法来解决这个问题。依题,采用线性探测再散列法处理冲突。下面详细介绍如何构建散列表:

第一个key 7,它的地址是0,因此放到散列表的数组下表为0的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

第二个key 8,它的地址是3,因此放到散列表的数组下表为3的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

第三个key 30,它的地址是6,因此放到散列表的数组下表为6的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

第四个key 11,它的地址是5,因此放到散列表的数组下表为5的位置,这个位置上没有关键字,因此没有冲突可以直接填入;

第五个key 18,它的地址是5,因此放到散列表的数组下表为5的位置,但这个位置上已经有关键字11,遇到了冲突,此时我们根据线性探测再散列法来处理这个冲突,探测下一个位置6, 6这个位置上已经存在关键字30则继续增加步长1,因此现在的新地址应为7,位置7上没有关键字,放入即可,到此冲突已经解决;

第六个key 9,它的地址是6,因此放到散列表的数组下表为6的位置,但这个位置上已经有关键字30,遇到了冲突,探测下一个位置7, 7这个位置上已经存在关键字18则继续增加步长1,因此现在的新地址应为8,位置8上没有关键字,放入即可;

第七个key 14,它的地址是0,因此放到散列表的数组下表为0的位置,但这个位置上已经有关键字7,遇到了冲突,探测下一个位置1, 位置1上没有关键字,放入即可;

到这一步所有关键字均已填入,散列表已经构造完成,如表2所示。

(2)等概率情况下查找成功平均查找长度:

这一问可以根据第一问的构造过程求解:

key7一次就填入了表中,因此查找次数为1,同理8, 30, 11查找次数均为1; key18 进行了3次放入操作,探测位置分别是5,6,7 ,因此查找次数为3;key9也是3次;key14 进行了两次探测,因此查找次数为2。次数表如表3所示

所以ASLsuccess= (1+1+1+1+3+3+2)/ 7 = 12/7。

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

接下来讨论不成功的情况, 看表2,计算查找不成功的次数就直接找关键字到第一个地址上关键字为空的距离即可, 但根据哈希函数地址为MOD7,因此初始只可能在0~6的位置。等概率情况下,查找0~6位置查找失败的查找次数为:

看地址0,到第一个关键字为空的地址2的距离为3,因此查找不成功的次数为3.

地址1, 到第一个关键为空的地址2的距离为2,因此查找不成功的次数为2.

地址2,  到第一个关键为空的地址2的距离为1,因此查找不成功的次数为1.

地址3,到第一个关键为空的地址4的距离为2,因此查找不成功的次数为2.

地址4,到第一个关键为空的地址4的距离为1,因此查找不成功的次数为1.

地址5,到第一个关键为空的地址2(注意不是地址9,因为初始只可能在0~6之间,因此循环回去)的距离为5,因此查找不成功的次数为5.

地址6,到第一个关键为空的地址2(注意不是地址9,因为初始只可能在0~6之间,因此循环回去)的距离为4,因此查找不成功的次数为4.

因此查找不成功的次数表如下表所示

时间: 2024-07-31 14:35:04

哈希表的理解的相关文章

哈希表简易入门

什么是哈希表 哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构.也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度.这个映射函数叫做哈希函数,存放记录的数组叫做哈希表.哈希表作为一种高效的数据结构,有着广泛的应用.如果哈希函数设计合理,理想情况下每次查询的时间花费仅仅为 O(h/r),即和哈希表容量与剩余容量的比值成正比.只要哈希表容量达到实际使用量的大约 1.5 倍以上,查询花费的时间基本就可以认为恒为 O(1).

哈希表的运用

哈希表由于是以Key.Value的键值对形式存在,所以用起来特别方便,但是哈希表如何用呢?其实非常简单 第一步,我们要创建哈希表变量 Hashtable table = new Hashtable(); 第二步,我们要给哈希变量赋值,通过Add方法 table.Add("Name", "杨松"); table.Add("Code", 123456); 由于只是用来学习所以就手动创建两组数据,这样我们就把哈希变量创建好了, 如果我们要遍历哈希表,获

程序员,你应该知道的数据结构之哈希表

哈希表简介 哈希表也叫散列表,哈希表是一种数据结构,它提供了快速的插入操作和查找操作,无论哈希表总中有多少条数据,插入和查找的时间复杂度都是为O(1),因为哈希表的查找速度非常快,所以在很多程序中都有使用哈希表,例如拼音检查器. 哈希表也有自己的缺点,哈希表是基于数组的,我们知道数组创建后扩容成本比较高,所以当哈希表被填满时,性能下降的比较严重. 哈希表采用的是一种转换思想,其中一个中要的概念是如何将键或者关键字转换成数组下标?在哈希表中,这个过程有哈希函数来完成,但是并不是每个键或者关键字都需

深入理解哈希表

有两个字典,分别存有 100 条数据和 10000 条数据,如果用一个不存在的 key 去查找数据,在哪个字典中速度更快? 有些计算机常识的读者都会立刻回答: “一样快,底层都用了哈希表,查找的时间复杂度为 O(1)”.然而实际情况真的是这样么? 答案是否定的,存在少部分情况两者速度不一致,本文首先对哈希表做一个简短的总结,然后思考 Java 和 Redis 中对哈希表的实现,最后再得出结论,如果对某个话题已经很熟悉,可以直接跳到文章末尾的对比和总结部分. 哈希表概述 Objective-C 中

if 循环的深入理解 哈希表的一种应用

哈希表的值作为一个颜色容器,值默认为标识1, 表示未曾用过,若用过标识为0: 1: 程序第一步    遍历哈希表,查找标识为1 未曾用过的颜色 我用了这个: string colorno_use=""; foreach (string key in ht.Keys) { if(Convert.ToInt32(ht[key])==1)  //这个结果导致 所有为1的主键 都被循环了. 当然导致了后边的程序错乱问题.  { colorno_use = key; ht[key] = 0; +

Nginx 哈希表结构 ngx_hash_t

概述 关于哈希表的基本知识在前面的文章<数据结构-哈希表>已作介绍.哈希表结合了数组和链表的特点,使其寻址.插入以及删除操作更加方便.哈希表的过程是将关键字通过某种哈希函数映射到相应的哈希表位置,即对应的哈希值所在哈希表的位置.但是会出现多个关键字映射相同位置的情况导致冲突问题,为了解决这种情况,哈希表使用两个可选择的方法:拉链法 和 开放寻址法. Nginx 的哈希表中使用开放寻址来解决冲突问题,为了处理字符串,Nginx 还实现了支持通配符操作的相关函数,下面对 Nginx 中哈希表的源码

菜鸟nginx源码剖析数据结构篇(六) 哈希表 ngx_hash_t(上)

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 31h, 2014 1.哈希表ngx_hash_t的优势和特点 哈希表是一种典型的以空间换取时间的数据结构,在没有冲突的情况下,对任意元素的插入.索引.删除的时间复杂度都是O(1).这样优秀的时间复杂度是通过将元素的key值以hash方法f映射到哈希表中的某一个位置来访问记录来实现的,即键值为key的元素必定存储在哈希

Code Review:C#与JAVA的哈希表内部机制的一些区别

看C#与JAVA源码时发现C#与JAVA哈希表的实现略有不同,特此分享一下. 我觉得看哈希表的机制可以从"碰撞"这里划线分为两部分来分析. 1,发生碰撞前 在发生碰撞前决定get与put的速度唯一因素是通过哈希函数计算键值位置的速度.而占用空间大小取决于需要的桶的数量(取决于极限装载值(load factor)(假设已知需要放入哈希表中元素的数量)),和桶的大小. C#的默认装载系数=0.72 // Based on perf work, .72 is the optimal load

哈希表入门讲解

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构.也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度.这个映射函数称做散列函数,存放记录的数组称做散列表. 一个通俗的例子是,为了查找电话簿中某人的号码,可以创建一个按照人名首字母顺序排列的表(即建立人名{\displaystyle x}到首字母{\displaystyle F(x)}的一个函数关系),在首字母为W的表中查找"王"姓的电话号