算法及数据结构之散列表

散列表是支持 INSERT 、DELETE 和 SEARCH 的字典操作,其是对普通数组概念的推广,因为可以对数组元素进行直接寻址,故可在 O(1) 时间内访问数组的任意元素。

当实际存储的关键字数比可能的关键字总数较小时,这时采用散列表比直接的数组寻址更为有效,因为散列表通常采用的数组尺寸与索要存储的关键字数是成比例的。在散列表中,根据关键字计算出数组下标。

直接寻址表

当关键字的全域 U 比较小并且任意两个关键字都不相同时,其中每个关键字元素取自全域 U={0,1,...,m-1},此处 m 是一个不大的数,我们用一个数组(寻址表)T[0...m-1],其中每个位置对应全域 U 中的一个关键字。在这个表中进行字典操作的执行是很快的,只需 O(1) 的时间。

直接寻址技术一个明显的问题是,如果域U很大,在一台典型计算机的可用内存容量限制下,为其设计一个对应的直接寻址表T是不太现实的。

散列表

当实际存储在字典中的关键字集合 K 比所有可能的关键字域U小得多时,我们使用散列表。

在直接寻址方式下,具有关键字 k 的元素被存放到槽 k 中。在散列方式下,该元素处于 h(k) 中,也就是利用散列函数 h,根据关键字 k 计算出槽的位置,函数 h 将关键字映射到散列表 T[0...m-1] 的槽位上。

h:U→{0...m-1}

采用散列函数的目的就在于缩小需要处理的小标范围,即要处理的值从 |U| 降到了 m,从而降低了空间开销。

这样做存在一个问题,就是两个关键字很可能映射到同一个槽上,我们将这种情形成为发生了碰撞。

我们可以通过如下两种方式解决碰撞:

1)链表法

在链表法中,把散列到同一个槽中的所有元素都放在一个链表中。如槽j中有一个指针,它指向由所有散列到j的元素构成的链表的投,如果不存在这样的元素,则 j 中为 NIL。

用链表法散列的最坏情况是所有的关键字 n 都散列到同一个槽中,从而产生出一个长度为 n 的链表,这时最坏情况下查找的时间为 O(n)。散列方法的平均态依赖于所选取的散列函数 h 在一般情况下,将所有关键字分不在 m 个槽位上的均匀程度。假定任何元素散列到 m 个槽中的每一个的可能性相同,且与其他元素已被散列到什么位置上是独立无关的,这个假定称为简单一致散列。

一个好的散列函数应或近似地满足简单一致散列的假设。

将关键字解释为自然数:多数散列函数都假定关键字域为自然数集 N={0,1,2,...},如果给定的关键字不为自然数,则必须有一种方法来将他们解释为自然数。

a.除法散列法

通过取 k 除以 m 的余数,将关键字 k 映射到 m 个槽的某一个中去,即散列函数:

h(k)=k mod m

对于 m 的选择常常为与2的整数幂不太接近的质数。

b.乘法散列法

构造散列函数的乘法方法包含两个步骤,首先用关键字k乘上常数 A(0

h(k)=floor(m(kA mod 1))

其中 kA mod 1 为 kA 的小数部分,对于 A 的选择一般为:A 约等于(√5-1)/2,而 m 一般选择为2的某个幂次。

c.全域散列

任何一个特定的散列函数都有可能出现最坏的情况,使得平均检索时间为O(n),唯一有效的改进方法是随机地选择散列函数,使之独立于要存储的关键字,这种方法成为全域散列。

全域散列的基本思想是在执行开始时,从一族仔细设计的函数中,随机选择一个作为散列函数,随机化保住了没有哪一种输入会始终导致最坏情况形态,同时,随机化使得即使对同一个输入,算法在每一次执行的形态也都不一样,这样就可以确保对于任何输入,算法都具有较好的平均情况形态。

2)开放寻址法

在开放寻址法中,所有的元素都存放在散列表理,亦即每个表项或包含动态集合的一个元素或为NIL。当查找一个元素时,要检查所有的表项,直到找到所需的元素或发现元素不在表中。开放寻址法中的装在因子不能超过1

在开放寻址法中,当插入一个元素时,可以连续检查散列表的各项,直到找到一个空槽来放置待插入的关键字为止。检查的顺序不一定为0,1,2...,而是要依赖待插入的关键字,为了确定要探查哪些槽,我们将散列函数加以扩充,使之包含探查号(从0开始)以作为其第二个输入参数,如下面伪代码:

查找关键字 k 的算法的探查序列与将 k 插入时的插入算法一样,当查找到关键字或查找过程中碰到一个空槽时,查找算法就停止。

在开放寻址法中,当我们从槽 i 中删除关键字时,不能仅将 NIL 置于其中来标志它为空,如果这样的话,就会有个问题:在插入某关键字 k 的探查过程中,发现i被占用了,则 k 就被插入到后面的位置上,在将槽i中的关键字删除后,就无法检索关键字 k 了。

有一个办法就是在槽 i 中置一个特定的值 DELETED,而不是 NIL。这样 HASH-INSERT 做相应调整,使得 DELETED 标志的槽位仍可放入新的元素。

有三种技术常用来计算开放寻址法中的探查序列:线性探查、二次探查以及双重探查。

a.线性探查

首先给定一个普通的散列函数 h1(k),则线性探查的散列函数为:

h(k,i)=(h1(k)+i) mod m,i=0,1,...,m-1

线性探查容易实现,但存在一个问题称作一次群集,随着时间的推移,连续被占用的槽不断增加,平均查找时间也随着不断增加。群集现象很容易出现,因为一个空槽前有i个满的槽时,该空槽为下一个将被占用槽的概率为 (i+1)/m。

b.二次探查

二次探查采用如下形式的散列函数:

h(k,i)=(h1(k)+c1*i+c2*i2) mod m

其中h1是一个普通散列函数,c1、c2 为常数,i=0,1,...,m-1。如果两个关键字的初始探查位置相同,那么它们的探查序列也是相同的,这一性质可导致一种程度较轻的群集现象,称为二次群集。

c.双重散列

双重散列所产生的排列具有随机选择的排列的许多特性,它采用如下形式的散列函数:

h(k,i)=(h1(k)+ih2(k)) mod m

其中 h1 和 h2 为普通散列函数,初始探查位置为 T[h1(k)],后续探查位置在此基础上加上偏移量 h2(k),这里的探查序列以两种方式依赖于关键字k,因为初始探查位置、偏移量都可能发生变化。

d.完全散列

当关键字集合是静态的时(指各关键字存入表中后,关键字集合就不再变化了)。如果某一种散列技术在进行查找时,最坏情况内存访问次数为 O(1) 的话,则称其为完全散列。

设计一种完全散列:我们利用一种两级的散列方案,每一级都采用全域散列。

第一级与带链接的散列基本上是一样的:利用从某一全域散列函数族中仔细选出一个散列函数 h,将 n 个关键字映射到 m 个槽中。

对于第二级,我们不是对散列到槽j中的所有关键字建立一个链表,而是采用了一个较小的二次散列表 S,与其相关的散列函数 h1,可以确保在第二级上不出现碰撞。

本文作者:陈文龙(点融黑帮),现就职于点融网成都架构组担任研发工程师,平时喜欢听歌及户外运动。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 9.3.3 快速排序   快速排序将原数组划分为两个子数组,第一个子数组中元素小于等于某个边界值,第二个子数组中的...
    RichardJieChen阅读 1,833评论 0 3
  • 一、散列的概念 散列方法的主要思想是根据结点的关键码值来确定其存储地址:以关键码值K为自变量,通过一定的函数关系h...
    SeanMa阅读 63,968评论 1 30
  • 周末的图书馆,人特别多,二楼三楼都看遍了,愣是没有找出一个位置,索性就盘腿依书架席地而坐。 耳朵里依旧是那首辛德勒...
    话只说给懂得人听阅读 397评论 3 2
  • 这天相遇是在兰大的院内,大约是在进门后靠右边的那个食堂那儿相遇。 我这两天从她那儿学乖了。发现,原来不用带着学生证...
    劳伦斯公园阅读 340评论 0 0
  • "飞机飞过天空,天空之城,落日下的,黄昏的我们……。"不知怎么回事脑子里怎么就回放着这首歌,李志写这首词的时候脑...
    一个柿子阅读 440评论 0 0