首页 > 哲学范文 > 马克思主义哲学 / 正文
哈希表是一种高效数据结构。本文分五个部分首先提出
2021-02-28 11:14:35 ℃哈希表是一种高效的数据结构。本文分五个部分:首先提出了哈希表的优点,其次 介绍了它的基础操作,接着从简单的例子中作了效率对比,指出其适用范围以及特点, 然后通过例子说明了如何在题目中运用哈希表以及需要注意的问题,最后总结全文。
[ 正文 ]. 引言
哈希表( )的应用近两年才在中出现,作为一种高效的数据结构,它正在竞赛中 发挥着越来越重要的作用。
哈希表最大的优点, 就是把数据的存储和查找消耗的时间大大降低,几乎可以看成 是常数时间; 而代价仅仅是消耗比较多的内存。然而在当前可利用内存越来越多的情况 下,用空间换时间的做法是值得的。另外,编码比较容易也是它的特点之一。
哈希表又叫做散列表,分为 "开散列" 和"闭散列 " 。考虑到竞赛时多数人通常避免 使用动态存储结构, 本文中的 "哈希表 "仅指"闭散列 " ,关于其他方面读者可参阅其他书 籍。
. 基础操作 基本原理
我们使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数, 也叫做散列函数),使得每个元素的关键字都与一个函数值(即数组下标)相对应,于 是用这个数组单元来存储这个元素;也可以简单的理解为,按照关键字为每一个元素 分类 ",然后将这个元素存储在相应 "类"所对应的地方。
但是,不能够保证每个元素的关键字与函数值是一一对应的,因此极有可能出现对 于不同的元素,却计算出了相同的函数值,这样就产生了
" 冲突" ,换句话说,就是把不
同的元素分在了相同的 "类" 之中。后面我们将看到一种解决 "冲突"的简便做法。
总的来说, "直接定址 "与"解决冲突 "是哈希表的两大特点。
函数构造
构造函数的常用方法 (下面为了叙述简洁,设 () 表示关键字为 的元素所对应的 函数值):
) 除余法:
选择一个适当的正整数 ,令 ( )
这里, 如果选取的是比较大的素数,效果比较好。而且此法非常容易实现,因此 是最常用的方法。
) 数字选择法:
如果关键字的位数比较多, 超过长整型范围而无法直接运算,可以选择其中数字分 布比较均匀的若干位,所组成的新的值作为关键字或者直接作为函数值。
冲突处理
线性重新散列技术易于实现且可以较好的达到目的。
令数组元素个数为 ,则当 () 已经存储了元素的时候,依次探查
(()),
……,直到找到空的存储单元为止(或
者从头到尾扫描一圈仍未发现空单元,这就是哈希表已经满了,发生了错误。当然这是 可以通过扩大数组范围避免的)。
支持运算
哈希表支持的运算主要有:初始化 () 、哈希函数值的运算 (()) 、插入元素 () 、查找 元素 () 。
设插入的元素的关键字为 , 为存储的数组。
初始化比较容易,例如
; 用非常大的整数代表这个位置没有存储元素
;
表的大小
[];
哈希函数值的运算根据函数的不同而变化,例如除余法的一个例子:
();
我们注意到,插入和查找首先都需要对这个元素定位,即如果这个元素若存在,它 应该存储在什么位置,因此加入一个定位的函数
();
J
();
J
(<)([() ]<>)([() ]<>)
();
当这个循环停下来时,要么找到一个空的存储单元,要么找到这个元 素存储的单元,要么表已经满了
() ;
J
插入元素
();
();定位函数的返回值
();
定位函数的返回值
[][]
; 即为发生了错误,当然这是可以避免的查找元素是否已经在表中
();
();
[]
这些就是建立在哈希表上的常用基本运算。
下文提到的所有程序都能在附录中找到。
.效率对比简单的例子与实验
下面是一个比较简单的例子:
集合()问题描述:
给定两个集合、,集合内的任一元素满足 <
< 0,并且每个集合的元
素个数不大于 r个。我们希望求出、之间的关系。只需确定在 中但是不在 中 的元素的个数即可。(这个题目是根据
模拟赛 的第一题改编的。)
分析:我们先不管 与 的具体关系如何,注意到这个问题的本质就是对于给定 的集合,确定 中的元素是否在 中。所以,我们使用哈希表来处理。至于哈希 函数,只要按照除余法就行了,由于故意扩大了原题的数据规模,
();
当然本题可以利用别的方法解决,所以选取了速度最快的快速排序二分查 找,让这两种方法作效率对比。
我们假定,对于随机生成的数据,计算程序重复运行次所用时间。
对比表格如下:
哈希表() 快速排序二分查找 () () ( 只有忽略了冲突才是这个结果。当然
复杂度 实际情况会比这个大,但是重复的几率与 ( ) ( ) 哈希函数有关,不容易估计 )
测试数据规
模
对于数据的说明: 在 下用 测试,为了使时间的差距明显,让程序重复运了 行次。同时哈希表中的 ,下标范围 。由于快速排序不稳定,因此使用了随机 数据。
. 对实验结果的分析:
注意到两个程序的用时并不像我们期望的那样,总是哈希表快。设哈希表 的大小为 .
首先,当规模比较小的时候(大约为 < * ,这个数据仅仅是通过若干数据 估记出来的,没有严格证明,下同),第二种方法比哈希表快。这是由于,虽然 每次计算哈希函数用 () 的时间,但是这个系数比较大。例如这道题的 () , 通过与做同样次数的加法相比较, 测试发现系数 > ,因为 运算本身与快速排 序的比较大小和交换元素运算相比,比较费时间。所以规模小的时候, () (忽略 冲突)的算法反而不如 () 。这一点在更复杂的哈希函数上会体现的更明显,因 为更复杂的函数系数会更大。
其次,当规模稍大 (大约为 * < < * ) 的时候,很明显哈希表的效率高 这是因为冲突的次数较少。
再次,当规模再大 (大约为 * < < )的时候,哈希表的效率大幅下降。
这是因为冲突的次数大大提高了, 为了解决冲突, 程序不得不遍历一段都存储了 元素的数组空间来寻找空位置。
用白箱测试的方法统计, 当规模为的时候, 为了 找空位置, 线性重新散列平均做了 次运算; 而当规模为 的时候,平均竟然高达 次运算,某些数据甚至能达到次。
显然浪费这么多次运算来解决冲突是不合算的,
解决这个问题可以扩大表的规模,或者使用"开散列"(尽管它是动态数据结构) 然而需要指出的是,冲突是不可避免的。
初步结论:
当数据规模接近哈希表上界或者下界的时候,哈希表完全不能够体现高效 的特点,甚至还不如一般算法。但是如果规模在中央,它高效的特点可以充分体 现。我们可以从图像直观的观察到这一点。
其他方法哈希表
其他方法
哈希表
时间效率
实验表明当元素充满哈希表的
的时候,效率就已经开始明显下降。这就
给了我们提示:如果确定使用哈希表,应该尽量使数组开大(由于竞赛中可利用 内存越来越多,大数组通常不是问题,当然也有少数情况例外),但对最太大的 数组进行操作也比较费时间,需要找到一个平衡点。通常使它的容量至少是题目 最大需求的,效果比较好(这个仅仅是经验,没有严格证明)。
应用举例应用的简单原则
什么时候适合应用哈希表呢?如果发现解决这个问题时经常要询问:
"某个
元素是否在已知集合中?",也就是需要高效的数据存储和查找,则使用哈希表 是最好不过的了!那么,在应用哈希表的过程中,值得注意的是什么呢?
哈希函数的设计很重要。一个不好的哈希函数,就是指造成很多冲突的情 况,从前面的例子已经可以看出来,解决冲突会浪费掉大量时间,因此我们的目 标就是尽力避免冲突。前面提到,在使用"除余法"的时候,(),最好是一个 大素数。这就是为了尽力避免冲突。为什么呢?假设 ,则哈希函数分类的标准 实际上就变成了按照末三位数分类,这样最多类,冲突会很多。一般地说,如果 的约数越多,那么冲突的几率就越大。
简单的证明:假设 是一个有较多约数的数,同时在数据中存在
满足
() > ,即有* , *, 则有 * [ ] *[].
①其中[] 的取值范围
是不会超过[,]的正整数。也就是说,[] 的值只有 种可能,而 是一个 预先确定的数。因此 ①式的值就只有 种可能了。这样,虽然 运算之后的余 数仍然在[,]内,但是它的取值仅限于 ① 可能取到的那些值。也就是说余数 的分布变得不均匀了。容易看出, 的约数越多,发生这种余数分布不均匀的情 况就越频繁,冲突的几率越高。而素数的约数是最少的,因此我们选用大素数 记住" 素数是我们的得力助手 "。
另一方面,一味的追求低冲突率也不好。理论上,是可以设计出一个几乎 完美,几乎没有冲突的函数的。然而,这样做显然不值得,因为这样的函数设计 很浪费时间而且编码一定很复杂, 与其花费这么大的精力去设计函数, 还不如用 一个虽然冲突多一些但是编码简单的函数。
因此,函数还需要易于编码, 即易于 实现。
综上所述,设计一个好的哈希函数是很关键的。而 " 好"的标准,就是较低 的冲突率和易于实现。
另外,使用哈希表并不是记住了前面的基本操作就能以不变应万变的。有 的时候,需要按照题目的要求对哈希表的结构作一些改进。
往往一些简单的改进 就可以带来巨大的方便。
这些只是一般原则,真正遇到试卷的时候实际情况千变万化,需要具体问 题具体分析才行。下面,我们看几个例子,看看这些原则是如何体现的。
有关字符串的例子
我们经常会遇到处理字符串的问题,下面我们来看这个例子:
找名字 问题描述:
给定一个全部由字符串组成的字典,字符串全部由大写字母构成。其中为
每个字符串编写密码, 编写的方式是对于 位字符串, 给定一个 位数, 大写字
母与数字的对应方式按照电话键盘的方式:
题目给出一个 位的数,找出在字典中出现且密码是这个数的所有字符串。
字典中字符串的个数不超过 。(这个是 的一道题。)
分析: 看懂题目之后,对于给定的编码,只需要一个回溯的过程,所有可 能的原字符串都可以被列举出来, 剩下的就是检查这个字符串是否在给定的字典 中了。所以这个问题需要的还是 "某个元素是否在已知集合中? "由于给出的 "姓 名" 都是字符串,因此我们可以利用字符的
码。那么,如何设计这个哈希函数
呢?注意到题目给出的字典中,最多能有 个不同元素,而一个字符的 码只能 有 种不同的取值,因此至少需要用在个位置上的字符(A >,但是A<
),
于是我们就选取个位置上的字符。
由于给定的字符串的长度从 都有可能, 为了 容易实现, 选取最开始的个字符, 和最末尾的个字符。
让这个字符组成进制的位
数,则这个数的值就是这个字符串的编码。这样哈希函数就设计出来了!
不过,由于可能出现只有位的字符串, 在写函数代码的时候需要特殊考虑;
大素数选取 。
这个函数是这样的:
();
; { 用来记录进制数的值 }
()>
*([]);
([()]); {
取第一位和后两位 }
([]);{ 当长度为的时候特殊处理 }
值得指出的是, 本题给出的字符串大都没有什么规律, 用哈希表可以做到近
似"平均" ,但是对于大多数情况,字符串是有规律的(例如英文单词),这个时
候用哈希表反而不好 (例如英语中有很多以 开头的单词) ,通常用检索树解决
这样的查找问题。
在广度优先搜索中应用的例子
在广度优先搜索中, 一个通用而且有效的剪枝就是在拓展节点之前先判重。
而判重的本质也是数据的存储与查找, 因此哈希表大有用武之地。
来看下面的例
子:
转花盆 题意描述 :
给定两个正边形的花坛,要求求出从第一个变化到第二个的最小操作
次数以及操作方式。
一次操作是: 选定不在边上的一盆花, 将这盆花周围的盆花
按照顺时针或者逆时针的顺序依次移动一个单位。
限定一个花坛里摆放的不同种
类的花不超过种, 对于任意两种花, 数量多的花的盆数至少是数量少的花的倍
(这是 的一道题)
分析: 首先确定本题可以用广度优先搜索处理,然后来看问题的规模。正边形 共有个格子可以用来放花,而且根据最后一句限定条件,至多只能存在 () * () 种状态,用搜索完全可行。
然而操作的时候, 可以预料产生的重复节点是相当多 的,需要迅速判重才能在限定时间内出解, 因此想到了哈希表。
那么这个哈希函 数如何设计呢?注意到个格子组成边形是有顺序的, 而且每一个格子只有种可能
情况,那么用进制位数最大A用 完全可以承受。于是我们将每一个状态与 个整数对应起来,使用除余法就可以了。
小结
从这两个例子可以发现,对于字符串的查找,哈希表虽然不是最好的方法, 但是每个字符都有"天生"的 码,在设计哈希函数的时候可以直接利用。
而其他 方法,例如利用检索树的查找,编写代码不如哈希表简洁。至于广度优先搜索中 的判重更是直接利用了哈希表的特点。
另外,我们看到这两个题目都是设计好哈希函数之后,直接利用前面的基 本操作就可以了,因此重点应该是在哈希函数的设计上(尽管这两个例子的设计 都很简单),需要注意题目本身可以利用的条件,以及估计值域的范围。下面我 们看两个需要在哈希表基础上作一些变化的例子。
需要微小变化的例子
下面,我们来分析一道 的试卷:
方 程的解数问题描述
已知一个元高次方程:
+& 花內 +
+knx/n = 0
其中:…_
-?是未知数,「卞…险是系数,:忙 n是指数。且方
程中的所有数均为整数。
假设未知数W甬W,,求这个方程的整数解的个数。
约束条件
| +陶M約|+……+忆必齐| < 231
方程的整数解的个数小于 1 。
本题中,指数(,……)均为正整数。
这个是
的第二试中的《方程的解数》。
分析:初看此题,题目要求出给定的方程解的个数,这个方程在最坏的情况 下可以有个未知数,而且次数由输入决定。这样就不能利用数学方法直接求出解
的个数,而且注意到解的范围最多个数, 因此恐怕只能使用枚举法了。
最简单的 思路是穷举所有未知数的取值,这样时间复杂度是
(八),无法承受。因此我们
需要寻找更好的方法, 自然想到能否缩小枚举的范围呢?但是发现这样也有很大 的困难。我们再次注意到 的范围,若想不超时,似乎算法的复杂度上限应该是(八) 左右,这是因为A < 。这就启示我们能否仅仅通过枚举个未知数的值来找到答 案呢?如果这样,前一半式子的值 可以确定,这时只要枚举后 个数的值,检 查他们的和是否等于 即可。这样只相当于在 (A) 前面加了一个系数,当然还 需要预先算出 到 的各个幂次的值。
想到了这里, 问题就是如何迅速的找到某 个 是否曾经出现过,以及出现过了多少次,于是又变成了 " 某个元素是否在给 定集合中 " 这个问题。所以,我们还是使用哈希表解决这个问题。至于哈希函数 不是问题, 还是把 的值作为关键字使用除余法即可。
然而有一点需要注意, 这 个例子我们不仅需要纪录某个 是否出现, 出现的次数也很重要, 所以可以用一 个维数组,仅仅是加了一个存储出现次数的域而已。
[] ; {[] 记录哈希函数值为 的 值, [] 记录这个 值出现了几次 } 因此 过程也需要一些变化:
();
();
[];
([]); { 仅仅这一条语句,就可以记录下来 出现了几次 }
最后一个例子
下面我们来仔细分析下面这个问题:
迷宫
的墙 题意描述 :
神话中山边有一个井之迷宫。迷宫的入口在山顶。迷宫中有许多房间,每 个的颜色是以下之一: 红、绿、蓝。两个相同颜色的房间看起来相似而不可区分。
每个房间里有三口井标以。
从一个房间到另一间只有一种方式: 从上面房间的井 里跳到(不一定竖直地)井底的房间。可以从入口房间到达任何其他房间。迷宫 中的所有通路走向坐落在最底部的龙宫。
所有的迷宫之旅对应了一系列在相继访 问的房间里选择的井的标号。
这一列数称为一个旅行计划。
一个走过好几次迷宫 的英雄画好了图,然而有的房间重复出现了多次。
输入:
第一行有一个整数 <<,房间数(包括龙宫)。房间从1到标号,较大编号 的房间再较低处(入口房间编号1,龙宫编号)。接下的行描述迷宫的房间(除 了龙宫)和井。每行有一个字母,一个空格,和三个由空格分隔的整数。字母代 表了房间的颜色(红,绿,蓝),第 () 个数是第个井通往的房间号。
输出:
迷宫最少的房间数目
这是 中国国家集训队难题讨论活动的 题。
分析: 题目的意思是给出这个迷宫的地图,去掉重复出现的房间,找出这 个迷宫的最少房间数目。
于是关键就是确定什么样的房间是重复的。
通过对样例 的分析,可以看出这样的房间是重复的:如果两个房间 和 (< < ),他们的 颜色相同,而且第 () 堵墙通向的房间或者相同、或者重复。因为这样从 和 可到达的房间是完全相同的。
所以,我们只需要记录下每个房间的情况和已经被确定相同的房间,然后 挨个比较即可。于是又需要用到高效的数据存储与查找, 自然想到哈希表。
然而, 这里面需要对哈希表作更大的改进: 首先每个房间只能是种颜色之一, 因此针对 每种颜色分别建立哈希表, 可以使哈希函数的自变量减少一个; 其次还需要纪录 每个不重复的房间每堵墙都通向哪个房间,还有哪些房间是重复的。
具体这样实现:
[] ; { 代表共有种颜色, 是哈希函数的值域,而 中的 表示三堵墙连 接到那个房间, 表示这个单元存储的是哪个节点 }
[] ; {[] 表示与 相同的节点。如果有多个节点都是相同的,择取其中最 大的(这一点不需要特殊的操作,只要在处理节点的时候注意就行了) }
至于哈希函数,最开始我是随意的写了一个(因为越是随意的,就越是随 机的!),定位函数是这样的:
( );
[]*[]*[]*; {
用堵墙的值任意乘大素数相加再取余数,使得结果分
布比较随机,也就比较均匀 }
([,() ]<>)([,() ]<>[])
线性重新散列 }
线性重新散列 }
但是后来发现完全没有必要这样做, 这样的哈希函数在计算 的时候浪费了 很多时间(不过数据规模不是很大,所以这点不十分明显),而且素数起到的作 用也不应当是这样的。
其实让 [][][] 组成 进制数就完全能够达到目的了, 加 入了素数不仅是小规模数据计算浪费时间, 对大数据最后结果的分布平均也没有 起到比 进制数更多的作用。因此改为
[]*()[]*[];
当然肯定会有更好的哈希函数的。
小结
第一个例子,乍一看与哈希表毫无关系;第二个例子叙述比较复杂,但是 经过仔细分析, 发现问题的本质都是确定 "某个元素是否在给定集合中 ",这正是 哈希表的特点。
所以,不论题目的表面看起来如何, 只要本质是需要高效的数据 检索,哈希表通常就是最好的选择!
另外,这两个例子都在原来哈希表的基础上作了一些变化。第一个例子加 入了纪录某个值出现次数的域, 第二个例子加入了纪录所有墙的情况以及原节点 编号的域。
虽然都只是很小的变化, 但是却给问题的解决带来了不小的方便。
因 此我们得到提示: 哈希表虽然有标准的操作, 但也不是一成不变的, 需要具体问 题具体分析,根据题目的要求和特点作出相应变化。
- 上一篇:2020年局党组民主生活会个人对照检查3篇
- 下一篇:2021运动会开幕式校长讲话
猜你喜欢
- 2024-01-20 2024年校长发言:“两个结合”是保持马克思主义蓬勃生机时代要求【精选推荐】
- 2024-01-19 2024主题党课:中国化马克思主义
- 2024-01-14 参加“青年马克思主义培训班”心得体会【优秀范文】
- 2024-01-08 2024年校长发言:“两个结合”是保持马克思主义蓬勃生机时代要求【精选推荐】
- 2023-12-26 2024年校长发言:“两个结合”是保持马克思主义蓬勃生机时代要求【精选推荐】
- 2023-08-03 马的优秀作文6篇(完整文档)
- 2023-07-01 2023年度党建引领马克思主义学院高质量发展工作总结
- 2023-06-12 2023年度幸福哲学
- 2023-05-31 纪念马克思诞辰200周年活动方案(范文)(完整)
- 2023-05-15 2023党课:马克思共同富裕思想中资本逻辑批判及其现实启示
- 搜索
-
- 《中华人民共和国公职人员政务处分法》 07-21
- 新时代中国青年与马克思主义信念4篇 05-12
- 反假考试2019理论模拟试题(一) 05-04
- 关于申请解决临时聘用人员工资的报告 05-07
- 关于“执法规范化”学习心得体会 04-10
- 十四五规划编制指示学习心得体会发言2 10-04
- 内部控制开题报告 10-11
- 2020年党支部工作情况报告 07-01
- 体检优质护理服务措施 04-06
- 坚持和巩固党对意识形态工作的领导发言 04-28
- 11-25国庆70周年庆典晚会 庆典晚会串词
- 11-25办公室礼仪的十大原则 浅谈办公室的电话礼仪
- 01-17用心灵轻轻地歌唱_心灵的歌唱
- 01-17也许你不是我一生的唯一|也许不是我
- 01-17爱了,请珍惜;不爱,趁早放手|爱就珍惜不爱就放手
- 01-17岁月带走的是记忆,但回忆会越来越清晰|有趣又有深意的句子
- 01-17曾经的美好只是曾经,我只想珍惜身边的人|我只想珍惜你
- 01-18从容不惊 [学会笑眼去看世界,不惊不乍,淡定从容]
- 02-03当代大学生学习态度调查报告
- 02-03常用护患英语会话
- 标签列表