数据结构中散列表的复习笔记

除了各种树表之外,还可以采用散列技术来表示并实现动态查找表。“散列”既是一种存储方式,又是一种查找方法。这种查找方法称为散列查找。按散列存储方式构造的存储结构称为散列表。散列技术的核心是散列函数。

  散列函数是一种将键值映射为散列表中的存储位置的函数。对任意给定的动态查找表T,如果选定了某个“理想的”散列函数H及相应的散列表L,则对T中的每个数据元素X,函数值 H(X.key)就是X在散列表L中的存储位置。插入(或建表)时数据元素X将被安置在该位置上,并且查找X时也到该位置上去查找。

由散列函数决定的数据元素在散列表中的存储位置称为散列地址。因此,散列的基本思想是通过由散列函数决定的键值(X.key)与散列地址(H(X.key))之间的对应关系来实现存储组织和查找运算。

在理想的情况下,散列函数是一个一一对应,即每个键值对应于一个散列地址,且不同的键值对应不同的散列地址。但在实际应用中,这种情况很少出现。在大多数情况下,出现“同义词”并发生“冲突”是不可避免的。

  设有散列函数H和键值k1、k2,若k1<>k2且H(k1)=H(k2),则称k1、k2是(相对于H的)同义词。假如动态查找表中有两个数据元素X1、X2存入同一个散列表,而且它们的键值是同义词,这种情况称为冲突。

当然,我们希望同义词尽量少以便减少冲突。另一方面,由于冲突的不可避免性,又必须考虑在冲突发生时的处理办法。因此,采用散列技术时需要考虑的两个问题是:

  ①如何构造(选择)"均匀的"散列函数?

  ②用什么方法解决冲突?

当然,还需考虑散列表本身的组织方法。下面分别加以讨论。

一、散列函数的构造法

  这里介绍几种常用的构造方法。按这些方法构造出来的散列函数计算简单而且比较"均匀"(即同义词较少)。以下假定散列地址是自然数。另外假定键值也都是自然数(事实上,键值通常总可以转换为自然数)。

⒈数字分析法

  数字分析法又称为数字选择法,适用于下述场合:事先知道所有可能出现的键值,并且键值的位数比散列地址的位数多.在这种情况下可以对键值的各位进行分析,选择分布较均匀的若干位组成散列地址。

  假定已知可能出现的所有键值中的一部分如下:

        0 0 1 3 1 9 4 2 1

        0 0 1 6 1 8 3 0 9

        0 0 1 7 3 9 4 3 4

        0 0 1 6 4 1 5 1 6

        0 0 1 8 1 6 3 7 8

        0 0 1 1 4 3 3 9 5

        0 0 1 2 4 2 3 6 3

        0 0 1 9 1 5 4 0 9

           ……

不难看出,(左起)前三位分布不均匀,第5、7位亦有很多重复,故应将这五位丢弃。剩下的第4、6、8、9位都是分布较均匀的,可考虑它们或它们中的几位组合起来作为散列地址。至于到底选其中的几位,需进一步考虑动态查找表的容量(即其中数据元素的最大数目)以及散列表组织形式。

2.除余法

  除余法是一种简单有效的构造法。这种方法不要求事先知道全部键值。其作法 :选择一个适当的正整数P,以键值除以P所得的余数作为散列地址,即令散列函数H为

             H(key) = key % p

  这一方法的关键在于P 的选取。若选P为偶数,则所得的散列函数总是将奇数键值映射成奇数地址。偶数键值映射为偶数地址。因而增加了冲突的机会。若选P曾键值基数的幂,所得的散列地址实际上是键值的末几位,这也不好。通常选P为小于或等于散列表容量的最小素数。

3.平方取中法

  一个数的平方的中间几位与这个数的每一位都有关。利用这一特点,平方限中法以键值平方的中间几位作为散列地址。这一方法计算简单且不需要事先掌握键值的分布情况。又因平方限中能扩大键值差别,所得散列地址比较均匀。

4.基数转换法

  将键值看成另一种进制的数再转换成原来进制的数,然后选其中几位作为散列地址。例如,对于十进制键值210485,先把它看成是十三进制的数,并转换为十进制数:

    21048513=2×135+1×134+0×133+4×132+8×13+8=77193510

  然后从中选取几位作为散列地址。通常要求两个基数互素,且新基数比原基数大。

5.随机数法

  选择一个随机函数random,以键值在该函数下的值作为散列地址:

    H(key)= random (key)

  当各个键值的位数不同时采用此法较好。

二、动态查找表在开散列表上的实现

  采用散列技术需考虑的第二个主要问题是如何解决冲突。而处理冲突的方法与散列表本身的组织形式有关。按组织形式的不同,通常有两类散列表:开散列表与闭散列表。一旦选定了散列函数和散列表的组织形式,就可以确定相应的解决冲突的方法,进而可以给出动态查找表的一个具体实现。本小节先讨论动态查找表在开散列表上的实现。

  开散列表的组织方式如下。设选定的散列函数为H ,H的值域(即散列地址的集合)这0..n-1。设置一个"地址向量"pointer HP[n],其中的每个指针HP[i]指向一上单链表,该单链表用于存储所有散列地址为 i的数据元素、即所有散列地址为 i的同义词。每一个这样的单链表称为一个同义词子表。由地址向量以及向量中的每个指针所指的同义词子表构成的存储结构称为开散列表。

在开散列表中,所有键值为同义词的数据元素存在同一个同义词子表中,而地址向量中的元素是些同义词子表的表头指针。这就是开散列表解决冲突的方法。这一方法有时称为“拉链法”。具体的散列函数、散列表和处理冲突的方法这三个密相关的部分或方面确定了动态查找表的一个具体的散列存储结构。在此基础之上,可以进一步考虑动态查找表基本运算的实现。

三、开散列表与闭散列表的比较

  开散列表与闭散列表的差别类似于单链表与顺序表的差别。开散列表利用链接方法存储同义词,不产生堆积现象,且使得动态查找的基本运算特别是查找、播入和删除易于现实。但由于附加指针域而增加了存储开销。闭散列表无需附加指针域,因此存储效率较高。但由此带来的问题是容易产生堆积,而且某些基本运算不易实现。由于空闲位置是查找不成功的条件,实现删除运算时不能简单地将待删结点置空,否则将截断该后继散列地址序列的查找路径。因此闭散列表上的删除只能在待删结点上做标记。仅当运行到一定阶段进,经过整体考虑和整理,才能真正删除有标记的结点。

  散列技术的原始动机是无需键值比而完成查找。由于同义词(及堆积现象)的存在,这个"理想"并未完全实现。对开散列表来说,仍需将给定值与同义词子表中各结点的键值比较;对于闭散列表,则需将给定值与后继散列地址序列中结点的键值比较。由于开散列表不产生堆积,其平均查找长度较短。

  最后,由于开散列表中各同义词子表的表长是动态变化的,无需事先确定表的容量(开散列表由此得名);而闭散列表却必须事先估计容量。因此,开散列表更适合于事先前难以估计容量的场合。

需要补充说明的是,线性探测法和二次探测法在本书中是作为闭散列表的解决冲突的方法而提出的。但在某些教科书上,这两种方法又称为"开放定址法"。注意不要将这种"开放"(其含义为散列地址对一切数据元素开放)与本书的"开散列表"(其含义为散列表的存储空间是开放的)相混淆。

时间: 2024-10-18 08:46:21

数据结构中散列表的复习笔记的相关文章

Python学习笔记(3)--数据结构之列表list

Python的数据结构有三种:列表.元组和字典 列表(list) 定义:list是处理一组有序项目的数据结构,是可变的数据结构. 初始化:[], [1, 3, 7], ['a', 'c'], [1, 's', 'des',256]等 1.增加:append(value).extend(list2).insert(i, value) 2.删除:pop([i]).remove(value) 2.1  i可以是负值 2.2  i超出范围会报out of range错误 2.3  remove只会移除第

Windows 程序设计 复习笔记(共 77 问)

Windows 程序设计 复习笔记(共 77 问) (个人整理,仅做复习用 :D,转载注明出处:http://blog.csdn.net/hcbbt/article/details/42706501) 知识点 双字节字符集和Unicode字符集有何区别?采用双字节字符集有何问题 双字节字符集(DBCS)编码是0-255,DBCS含有1字节代码与2字节代码,而Unicode是统一的16位系统,这样就允许表示 65536个字符.Unicode中的每个字符都是16位宽而不是8位宽.在Unicode中,

安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)

什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再重复. 什么是Fragment? Fragment是Android3.0后新增的概念,Fragment名为碎片,不过却和Activity十分相似,具有自己的生命周期,它是用来描述一些行为或一部分用户界面在一个Activity中,我们可以合并多个Fragment在一个单独的activity中建立多个UI面板,或

2014年软考程序员-常考知识点复习笔记【第二章】

51CTO学院,在软考备考季特别整理了"2014年软考程序员-常考知识点复习笔记[汇总篇]",帮助各位学院顺利过关!更多软件水平考试辅导及试题,请关注51CTO学院-软考分类吧! 查看汇总:2014年软考程序员-常考知识点复习笔记[汇总篇]  二叉树三种遍历的非递归算法(背诵版) 1.先序遍历非递归算法 #define maxsize 100 typedef struct { Bitree Elem[maxsize]; int top; }SqStack; void PreOrderU

2014年软考程序员-常考知识点复习笔记【第三章】

51CTO学院,在软考备考季特别整理了"2014年软考程序员-常考知识点复习笔记[汇总篇]",帮助各位学院顺利过关!更多软件水平考试辅导及试题,请关注51CTO学院-软考分类吧! 查看汇总:2014年软考程序员-常考知识点复习笔记[汇总篇]  2.线性表 (1) 性表的链式存储方式及以下几种常用链表的特点和运算:单链表.循环链表,双向链表,双向循环链表. (2)单链表的归并算法.循环链表的归并算法.双向链表及双向循环链表的插入和删除算法等都是较为常见的考查方式. (3)单链表中设置头指

2014年软考程序员-常考知识点复习笔记【第五章】

51CTO学院,在软考备考季特别整理了"2014年软考程序员-常考知识点复习笔记[汇总篇]",帮助各位学院顺利过关!更多软件水平考试辅导及试题,请关注51CTO学院-软考分类吧! 查看汇总:2014年软考程序员-常考知识点复习笔记[汇总篇]  内部排序 考查你对书本上的各种排序算法及其思想以及其优缺点和性能指标(时间复杂度)能否了如指掌. 排序方法分类有:插入.选择.交换.归并.计数等五种排序方法. (1)插入排序中又可分为:直接插入.折半插入.2路插入(?).希尔排序.这几种插入排序

安卓开发复习笔记——Fragment+FragmentTabHost组件(实现新浪微博底部菜单)

记得之前写过2篇关于底部菜单的实现,由于使用的是过时的TabHost类,虽然一样可以实现我们想要的效果,但作为学习,还是需要来了解下这个新引入类FragmentTabHost 之前2篇文章的链接: 安卓开发复习笔记——TabHost组件(一)(实现底部菜单导航) 安卓开发复习笔记——TabHost组件(二)(实现底部菜单导航) 关于Fragment类在之前的安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)也介绍过,这里就不再重复阐述了. 国际惯例,先来张效果图: 下面

Python数据结构——散列表

散列表的实现常常叫做散列(hashing).散列仅支持INSERT,SEARCH和DELETE操作,都是在常数平均时间执行的.需要元素间任何排序信息的操作将不会得到有效的支持. 散列表是普通数组概念的推广.如果空间允许,可以提供一个数组,为每个可能的关键字保留一个位置,就可以运用直接寻址技术. 当实际存储的关键字比可能的关键字总数较小时,采用散列表就比较直接寻址更为有效.在散列表中,不是直接把关键字用作数组下标,而是根据关键字计算出下标,这种 关键字与下标之间的映射就叫做散列函数. 1.散列函数

计算机图形学 复习笔记

计算机图形学 复习笔记 (个人整理,仅做复习用 :D,转载注明出处:http://blog.csdn.net/hcbbt/article/details/42779341) 第一章 计算机图形学综述 研究内容 图形的概念:计算机图形学的研究对象 能在人的视觉系统中产生视觉印象的客观对象 包括自然景物.拍摄到的图片.用数学方法描述的图形等等 图形的要素 几何要素:刻画对象的轮廓.形状等 非几何要素:刻画对象的颜色.材质等 图形表示法 点阵表示 枚举出图形中所有的点,简称为图像. 参数表示 由图形的