《算法导论》— Chapter 11 散列表

1 序

在很多应用中,都要用到一种动态集合结构,它仅支持INSERT、SEARCH以及DELETE三种字典操作。例如计算机程序设计语言的编译程序需要维护一个符号表,其中元素的关键字为任意字符串,与语言中的标识符相对应。实现字典的一种有效数据结构为散列表。

散列表是普通数组的推广,因为可以对数组进行直接寻址,故可以在O(1)的时间内访问数组的任意元素。对于散列表,最坏情况下查找一个元素的时间与在链表中查找的时间相同,为O(n),但是在实践中,散列表的效率通常是很高的,在一些合理的假设下,散列表中查找的期望时间为O(1)。

2 直接寻址表

当关键字的全域U比较小时,直接寻址是一种简单而有效的技术。

为表示动态数组,定义一个数组(直接寻址表)T[0…m-1],其中每个位置对于全域U中的一个关键字。具体方法如下图所示:

字典操作实现伪码:

//查询操作
DirectAddressSearch(T , k)
    return T[k];

//插入操作
DirectAddressInsert(T , x)
    T[key[x]] = x;

//删除操作
DirectAddressDelete(T , x)
    T[key[x]] = NULL;

对于直接寻址表时间复杂度很低,但是其存在的问题是,需要覆盖全域的内存容量,空间复杂度高。因此,对于全域U很大而且内存容量不足的应用问题,直接寻址表不是一个理想的解决方案。

3 散列表

在直接寻址表中,具有关键字k的元素就被放到相应的槽k中。在现在所讨论的散列表中,关键字k的元素的映射位置将由一个散列函数h(k)计算得到。

显然的,采用此种方法,内存空间的占用从全域|U|减少到关键字的个数m,大大节省了内存开销。

但是,这样做会带来问题,两个或多个关键字可能会映射到同一个槽上,也就是所说的碰撞冲突。这与散列函数(下一节介绍)的选取息息相关,当然,我们不仅需要通过精心设计的随机散列函数来减少碰撞,也需要思考找到解决有可能出现碰撞的办法。接下来详细介绍几种散列函数与碰撞冲突解决策略。

4 散列函数

散列函数h(k)是计算关键字k映射位置的一种函数。对于全域U中的m个关键字,一个好的散列函数应该近似的满足简单一致散列的假设:每个关键字等可能的散列到m个槽位的任何一个中去,并与其他的关键字映射到哪个槽位无关。在实践中,通常运用启发式技术来构造好的散列函数,一种好的做法是以独立于数据中可能存在的任何模式的方式导出散列值,例如接下来介绍的“除法散列”。

最开始介绍的直接寻址表也是一种散列方式,其h(k)=k,除此之外,下面介绍其它三种散列方式。

4.1 除法散列法

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

h(k)=kmodm

应用除法散列方式的关键在于m的选择。

4.2 乘法散列法

构造散列函数的乘法方法包括两个步骤:

第一步,用关键字k乘以常数A(0 < A < 1)并取出kA的小数部分;

第二步,用m乘以求出的小数部分,在去结果的底值;

散列函数为:

h(k)=floor(m(kAmod1))

乘法散列的一个优点是其对m的选择没有特殊要求,一般设为2的某个次幂。

4.3 全域散列

以上讨论的散列方法都不可避免的会出现最坏情况,即所有关键字映射到同一个槽内,这是平均检索时间为O(n)。其实,任何一个特定的散列函数都可能出现最坏情况,唯一有效的改进方法为随机的选取散列函数,使之独立于要存储的关键字,这种方法也被称为全域散列,该方法的平均性能最佳。

全域散列性态讨论详见《算法导论》P139~P141。

5 碰撞冲突解决策略

5.1链接法

链接法是一种最简单的碰撞解决技术,该方法选择把散列到同一个槽中的元素都放在一个链表中。例如槽j中有一个指针指向所有散列到j的元素构成的链表的头,如果没有元素映射到此,则该指针为nil。

链接法解决碰撞冲突后,散列表T上的字典操作就很容易实现了。

这里写代码片

由以上讨论可以看出,链接法解决冲突后,对于散列表的插入操作始终可以在O(1)内实现,查找操作时间复杂度与该元素所在链表的长度成线性关系,而删除操作对于双向链表删除一个元素x(指针结点)同样可以在O(1)实现,若是单链表必须首先根据输入参数查找目标结点的前一个结点,故其与查找操作的复杂度相同。

5.2 开放寻址法

i 线性探测

ii 二次探测

iii 双重散列

5.3完全散列

时间: 2024-10-10 16:05:04

《算法导论》— Chapter 11 散列表的相关文章

算法导论——lec 11 动态规划及应用

和分治法一样,动态规划也是通过组合子问题的解而解决整个问题的.分治法是指将问题划分为一个一个独立的子问题,递归地求解各个子问题然后合并子问题的解而得到原问题的解.与此不同,动态规划适用于子问题不是相互独立的情况,即各个子问题包含公共的子子问题.在这种情况下,如果用分治法会多做许多不必要的工作,重复求解相同的子子问题.而动态规划将每个子问题的解求解的结果放在一张表中,避免了重复求解. 一. 动态规划介绍 1. 动态规划方法介绍: 动态规划主要应用于最优化问题, 而这些问题通常有很多可行解,而我们希

算法导论 第11章 散列表

散列表是主要支持动态集合的插入.搜索和删除等操作,其查找元素的时间在最坏情况下是O(n),但是在是实际情况中,散列表查找的期望是时间是O(1),散列表是普通数组的推广,因为可以通过元素的关键字对数组进行直接定位,所以能够在O(1)时间内访问数组的任意元素. 1.直接寻址表 当关键字的全域较小,即所有可能的关键字的量比较小时,可以建立一个数组,为所有可能的关键字都预留一个空间,这样就可以很快的根据关键字直接找到对应元素,这就是直接寻址表,在直接寻址表的查找.插入和删除操作都是O(1)的时间.. 2

《算法图解》chap5 散列表

线索Cues 笔记Notes 散列表的内部机制(实现,冲突,散列函数) 应用案例 一.散列表=散列函数+数组 散列函数特点: 将相同的输入映射到相同的数字 将不同的输入映射到不同的数字,但其实这样的函数几乎不会存在.所以会有冲突的存在:两个键分配的位置相同. 知道数组有多大,只返回有效的索引 散列表是由键和值组成的. 冲突的处理方式,当两个键映射到同一个位置,就在这个位置存储一个链表. 二. 查找某人的电话号码 投票,一人只能投一次.如果没有投过票就投票成功并且保存.投过就不让投 缓存:网站将数

算法导论第11章散列表11.1直接寻址表

/* * IA_11.1DirectAddressTables.cpp * * Created on: Feb 11, 2015 * Author: sunyj */ #include <stdint.h> #include <iostream> #include <string.h> // DIRECT-ADDRESS-SEARCH(T, k) // return T[k] // DIRECT-ADDRESS-INSERT(T, x) // T[x.key] = x

算法导论 16.1-1

用动态规划方法求解活动选择问题,与贪心算法相比,显然是庞然大物,大材小用了,贪心算法可以非常简洁的求解活动选择问题 动态规划: 1 #include <iostream> 2 #include <vector> 3 #include <limits> 4 #define INF numeric_limits<int>::max() 5 using namespace std; 6 int act_select(int s[],int f[],int n,in

算法导论9.1-1

寻找第二小的元素: 如果用堆排序的方法,我们建立一个堆后只需要,比较根节点的左儿子和右儿子的大小就可以得到第二小的节点了.而且BuildHeap的代价只有O(n). 1 #include<iostream> 2 3 using namespace std; 4 5 #define Left(i) i*2+1 6 #define Right(i) i*2+2 7 8 int size = 10; 9 10 void Exchange(int &a, int &b) 11 { 12

算法:开放定址法散列表

hash.h #ifndef _HASHQUAD_H #define _HASHQUAD_H #define MinTableSize 10 struct HashEntry; struct HashNode; typedef char *ElementType; typedef unsigned int Index; typedef Index Position; typedef struct HashNode *HashTable; typedef struct HashEntry *Cel

散列表的开放寻址法

开放寻址法(open addressing)中,所有元素都存放在槽中,在链表法散列表中,每个槽中保存的是相应链表的指针,为了维护一个链表,链表的每个结点必须有一个额外的域来保存它的前戏和后继结点.开放寻址法不在槽外保存元素,不使用指针,也不必须为了维护一个数据结构使用额外的域,所有可以不用存储指针而节省的空间,使得可以用同样的空间来提供更多的槽,也潜在地减少了冲突,提高了检索速度. 为了使用开放寻址法插入一个元素,需要连续地检查散列表,或称为探查(probe),直到找到一个空槽来放置待插入的关键

散列表查找的一个实例

这里解决冲突的方法是开放地址法:“开放地址指的是表中尚未被占用的地址,开放地址法就是当冲突发生时候,形成一个地址序列,沿着这个序列逐个进行探测,直到找到一个空的开放地址,将发生冲突的关键字存放到该地址中去,即Hi=(H(key)+di)%m,i=1,2,..k(k<=m),其中H(key)为散列函数,m为散列表长,di为增量序列. 例题:选取散列函数H(K)=(3K)%11,用开放地址处理冲突,d1=H(K);di=(di+(7K)%10+1)%11(i=2,3,..),试着在HT[0,..10