数据结构与算法分析~笔记8查找

8.1 查找的基本概念

以下表所示的学生基本信息表为例,先介绍查找的相关概念和术语。
在这里插入图片描述
数据项是数据不可分割的最小单位。如表中“学号”、“姓名”、“月”、“年级”等。每个数据项都必须有名称,该名称被称为数据项名字段名。在查找表中,要求同一个数据项的数据(字段的值)是同一种数据类型。组合项是由若干项组合构成的数据项。
组合项是由若干项组合构成的数据项。如表中的数据项“出生日期”就是一个组合项,它由“年”“月”“日”3项组成。
数据元素是由若干数据项、组合项构成的数据单位,一般作为整体进行考虑和处理,也称为记录。如表中对应一个学生的一行信息就是一个数据元素,一共有7个数据元素。
查找表(Search Table)是由同一类型的数据元素(或记录)构成的集合。由于集合中的数据元素之间存在着完全松散的关系,因此查找表是一种灵便的数据结构。
关键字(Key)是指可以标识一个数据元素(或记录)的某个数据项,关键字的值称为键值(Keyword)。若关键字可以唯一地标识一个记录,则称此关键字为主关键字(Primary Key);反之,则称此关键字为次关键字(SecondaryKey)。如表中的“学号”是主关键字,“姓名”、“性别”、“出生日期”都是次关键字。
查找(Search)是指在查找表中找出满足给定条件的数据元素(或记录)。对查找表经常进行的操作有:(1)查询某个“特定的”数据元素是否在表中;(2)检索某个“特定的”数据元素的各种属性;(3)在查找表中插入一个数据元素;(4)从查找表中删去某个数据元素。
若在查找表中找到了与给定值相匹配的记录,则称查找成功;否则,称查找失败
静态查找表(Static Search Table):仅对查找表进行查找操作,而不进行插入和删除操作的查找表。静态查找在查找不成功时,只返回一个不成功标志,查找的结果不改变查找表,因此表中数据元素的个数不会发生变化。
动态查找表(Dynamic Search Table):可对查找表进行查找、插入和删除操作的表。动态查找在查找不成功时,需要将被查找的记录插入到查找表中,查找的结果可能会改变查找表。因此表中数据元素的个数可能会发生变化。
一般而言,各种数据结构都会涉及查找操作,例如线性表、树、图等。这些数据结构中的查找操作并没有被作为主要操作考虑,它的实现服从于数据结构。但是,在某些应用中,查找操作是最主要的操作,为了提高查找效率,需要专门为查找操作设置数据结构,这种面向查找操作的数据结构称为查找结构(Search Structure)。
本章讨论的查找结构有:
(1)线性表:适用于静态查找,主要采用顺序查找技术、折半查找技术;
(2)树表:适用于动态查找,主要采用二叉排序树的查找技术;
(3)散列表:静态查找和动态查找均适用,主要采用散列技术。
衡量一个算法的效率,通常会通过时间复杂度(算法执行的时间量级)、空间复杂度(算法所需辅助空间的空间量级)和算法稳定性等对其进行分析。对于查找算法而言,其所需的辅助空间通常较小,只需要一个或几个;而查找算法的基本操作是“将记录的关键字和给定值进行比较”,因此通常以“关键字与给定值的比较次数”作为衡量算法效率的方法,该比较次数的期望值,称为查找成功时的平均查找长度(Average Search Length,ASL)。
对于含有n个记录的表,查找成功时的平均查找长度为:在这里插入图片描述
Pi表示查找第i个记录的概率,且在这里插入图片描述;Ci表示为了找到表中关键字与给定值相等的第i个记录,已和给定值进行过比较的关键字个数,因此可以看出Ci与所选择的查找算法有关。

8.2 静态查找表

静态查找表的定义表明,它是一类仅可对其进行查找操作,而不能改变数据元素的查找表。
查找方法:
顺序查找(Sequential Search)又称为线性查找,是最基本的查找方法之一。
查找过程:从表的一端开始,向另一端将关键字逐个与给定值key进行比较,若当前比较到的关键字与key相等,则查找成功,并返回数据元素在表中的位置,若检索完整个表仍未找到与key相同的关键字,则查找失败,返回失败的信息。
顺序查找算法的操作步骤如下:
Step 1:将所要查找的值key放入数组的第一个存储单元(即下标为0的单元)。
Step 2:从数组最后一个数据元素开始,向前一个一个比较记录是否等于key,若相等,则返回该记录所在的数组下标;若扫描完所有记录都没有与key相等的记录,则返回0。
Step 3:若返回值>0,则查找成功,否则查找失败。
算法分析
Pi为查找的是表中第i个数据元素的概率,Pi满足条件在这里插入图片描述。Ci是查找到第i个记录所需完成的比较次数,Ci取决于所查记录i在表中的位置。如查找表中的最后一个记录时,仅需比较一次;而查找第一个记录时则需要比较n次。因此得到Ci=n-i+1。则顺序查找的平均查找长度ASL为:
在这里插入图片描述
若每个记录的查找概率相等,即Pi=1/n,则ASL为:
在这里插入图片描述
上述对平均查找长度的讨论是在每次都查找成功的假设下进行的。然而,虽然当记录数n很大时,查找成功的可能性比失败的可能性大,但查找失败的情况仍然可能会出现。当查找失败的情况不能忽略时,查找算法的平均查找长度应该为查找成功的平均查找长度与查找失败的平均查找长度之和。
现假设查找成功与失败的可能性相同,并且对每个记录的查找概率也相等,则Pi=1/2n。而在查找失败时,不论所查找的关键字为何值,其进行比较的次数均为n+1。因此在该条件下,顺序查找的平均查找长度为:
在这里插入图片描述
顺序查找的优点是算法简单,并且对于表的结构没有任何要求,无论记录是否按关键字有序均可以使用;其缺点也很明显,特别是当n很大时,查找效率非常低。而许多情况下,查找表中数据元素的查找概率不一定相等。为了提高查找效率,查找表需要根据“查找概率越高,比较次数越少”的原则进行设计。

有序表的查找
若查找表中的数据元素无序,则选择顺序查找的方式既简单又实用。但当查找表中的数据元素在顺序存储时是有序的情况下,为了提高查找效率,可以采用折半查找(Binary Search)来实现,折半查找又称为二分查找。由于折半查找的算法限制,采用折半查找的前提条件是查找表中必须是采用顺序存储结构的有序表。
折半查找的步骤如下:
设表长为n,low、high和mid分别指向待查记录所在区间的下界、上界和中间点,key为给定的待查值
。Step 1:设置初始区间指针:下界指针low=1,上界指针high=n,执行Step 2。
Step 2:若low>high,查找失败,返回查找失败信息;否则令在这里插入图片描述。并执行如下操作:
• 若待查值key小于ST.elm[mid].key,则在m的左半区进行查找,令high=m-1;重新执行Step 2。
• 若待查值key大于ST.elm[mid].key,则在m的右半区进行查找,令low=m+1;重新执行Step 2。
• 若待查值key等于ST.elm[mid].key,则查找成功,返回记录在表中的位置。
算法分析
折半查找以表的中间点为比较对象,将表划分为两个子表,对定位到的子表继续进行这种查找操作的过程。因此,对表中每个数据元素的查找过程可以表示为一个二叉树,并称这个用于描述折半查找过程的二叉树为折半查找判定树
对于折半查找而言,其查找成功时进行关键字比较的次数至多为在这里插入图片描述
假设有序表的长度在这里插入图片描述(即h=log2(n+1)),则描述折半查找的判定树是深度为h的满二叉树。假设表中每个记录的查找概率相等,为Pi=1/n,则查找成功时,折半查找的平均查找长度为:
在这里插入图片描述由该等式可知,当n较大时,会有一个近似的结果为:
在这里插入图片描述
折半查找的效率通常比顺序查找要高,但折半查找只适用于采用顺序存储结构的有序表。

分块查找
分块查找又称为索引顺序查找,是对顺序查找的一种改进。
但当查找表的记录满足分块有序时,可以采用分块查找方法。分块有序即整个查找表无序,但把查找表看作几个子表时,每个子表中的关键字是有序的。在分块查找方法中,形象的把待查顺序表的子表称为
采用分块查找时,需要建立一个索引表来对每个子表进行索引,索引表的每个索引项包含如下信息:子表的最大关键字和子表的起始地址。子表的最大关键字是指对应子表中最大关键字的值,子表的起始地址是指对应子表的第一个记录在整个表中的位置。带有索引表的待查顺序表称为索引顺序表。一个索引顺序表的示例:
在这里插入图片描述
分块查找的基本思想:在查找时,首先用待查值key在索引表中进行区间查找(即查找key所在的子表,由于索引表按最大关键字项有序,因此可以采用折半查找或者顺序查找),然后在相应的子表中对key进行顺序查找。
分块查找步骤:
Step 1:对于待查值key,在索引表中按某种查找算法将key与各个子表的最大关键字如ki,kj进行比较。若ki≤key<kj,则key可能在kj所对应的子表中。
Step 2:在Step 1所找到的子表中进行顺序查找。若找到关键字与key相等的记录,则查找成功,返回该记录所在的位置;否则查找失败,返回0。
算法分析
对于索引顺序表而言,其索引表中的项是按关键字有序的,则确定第二步的待查子表的查找方法可以用顺序查找,也可以用折半查找或者其他查找算法;而待查子表中的记录并不是有序的,所以在待查子表中只能采用顺序查找。
在进行分块查找时,通常将长度为n的表均匀地分为b块(子表),每块有m个记录,因此有b=[n/m]。若假设用顺序查找来确定块的位置,表中每个记录的查找概率相等,则对每块查找的概率为1/b,块中每个记录的查找概率为1/m。该条件下分块查找的平均查找长度为:
在这里插入图片描述
可以看出,其平均查找长度不仅和表长n有关,而且与块中的记录个数m有关。可证明,当[插图]时,分块查找的平均查找长度取到最小值,为在这里插入图片描述。而若采用折半查找来确定块,则分块查找的平均查找长度为:
在这里插入图片描述

二叉排序树
二叉排序树(Brinary Sort Tree)又称为二叉查找树,它或者是一棵空树,或者是具有下列性质的二叉树:
(1)若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
(2)若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
(3)它的左右子树也分别是二叉排序树。
根据二叉排序树的定义,它是记录之间满足一定次序关系的二叉树,中序遍历二叉排序树可以得到一个按关键字排序的有序序列,这也是二叉排序树的名称由来。通常用二叉链表作为二叉排序树的存储结构,其结点结构可复用二叉链表的结点结构。二叉排序树是一种动态数据结构,其插入和删除操作非常方便,无需大量移动元素:
在这里插入图片描述
通常用二叉链表作为二叉排序树的存储结构,其结点结构可复用二叉链表的结点结构。二叉排序树是一种动态数据结构,其插入和删除操作非常方便,无需大量移动元素。
1. 二叉排序树的查找
二叉排序树的查找方法是:首先将给定值key与根结点的关键字进行比较,若相等,则查找成功;若根结点的关键字大于key,则在根结点的左子树上进行查找;否则,在根结点的右子树上进行查找。该查找过程类似于折半查找。
二叉排序树的查找算法的执行步骤如下:
Step 1:若二叉排序树为空,则查找失败;否则执行Step 2。
Step 2:将给定值key与树的根结点关键字进行比较,若相等则查找成功;否则执行如下操作:
• 若给定值key小于根结点关键字,则在以该树的左孩子为根结点的子树上执行Step 1。
• 若给定值key大于根结点关键字,则在以该树的右孩子为根结点的子树上执行Step 1。
2. 二叉排序树的插入
在二叉排序树中插入一个新结点后,形成的二叉树仍然是二叉排序树。若待插入结点的关键字为key,则二叉排序树的插入方法是:首先在树中查找是否已有关键字为key的结点,若查找成功,则说明待插入结点已存在,不能插入重复结点。只有当查找失败时,才在树中插入关键字为key的新结点。因此,新插入的结点一定是一个新添加的叶子结点,且该结点必定是查找不成功时查找路径上最后一个结点的左孩子结点或右孩子结点。插入过程与查找过程基本一致,只是在查找失败时将关键字与给定值key相等的记录作为左子树或右子树插入到最后一个结点即可。
创建一棵二叉排序树实质是从空树出发,不断执行插入结点操作的过程,这是插入操作的典型应用。一棵二叉排序树的创建过程:
在这里插入图片描述
在插入结点的过程中,无需对其他结点进行移动,而只需改变其中某个结点的指针即可。而且中序遍历二叉排序树即可得到一个关键字有序的序列,这意味着一个无序序列可以通过构造二叉排序树得到一个有序序列,构造二叉排序树的过程便是对无序序列进行排序的过程。
3. 二叉排序树的删除
在二叉排序树中删除一个结点后,形成的二叉树仍然须是二叉排序树。因此相对于插入操作来说,删除操作更为复杂。
假设二叉排序树上要被删除的结点为p(此处p是指向待删结点的指针,下面提到的f、PL、PR也都是代表对应含义的指针),其双亲结点为f,其左孩子结点为PL,其右孩子结点为PR,而f的右孩子结点用fR表示。不失一般性,假设p是f的左孩子结点,则分如下3种情况进行讨论。
(1)若p结点为叶子结点,由于删去叶子结点并不影响整个树的特性,故此时直接将其双亲结点的指针修改为空指针即可。
(2)若p结点只有左子树PL或者只有右子树PR,此时只要将PL(或PR)代替p的位置,成为f的子树即可。
(3)若p结点的左子树PL和右子树PR均不为空,此时的操作相对较为复杂。为了保证删除p结点后,中序遍历各结点相对位置不变,可以按如下做法执行删除操作:设查找p结点右子树PR上的右子树为t,而PR的最左下结点为s,结点s的双亲结点为spar,将s结点的数据代替p结点的数据,若PR有左子树,则将s的右子树接到结点spar的左子树上;否则将s的右子树接到spar的右子树上,然后删去s结点。
示例:
在这里插入图片描述
算法分析
在二叉排序树上查找关键字是否等于给定值的结点的过程,恰好走了一条从根结点到该结点的路径。而与给定值的比较次数等于给定值的结点在二叉排序树中的层数。比较次数最少为1,且最多不超过树的深度。
折半查找中关键字与给定值比较次数也不超过折半查找判定树的深度,然而对于折半查找判定树而言,如果长度n确定,则其形状是唯一的。然而一个结点数为n的二叉排序树的形状却不唯一,且其形状取决于各个记录被插入到二叉排序树的先后顺序。这导致二叉排序树查找时效率不一致。
最好情况是二叉排序树平衡,即接近于折半查找判定树的形状。最坏情况是二叉排序树完全不平衡,比如每个非叶子结点均只含有右孩子。
一般而言,二叉排序树的查找性能在O(log2n)和O(n)之间,平均情况下为O(log2n),但是对于二叉排序树的平衡化处理仍然是非常有必要的。

B_树
上述介绍的查找方法都是适用于内部查找的方法,也称为内部查找法。这类查找方法的数据集不大,可以放在内存中,适用于对较小的文件进行查找,而不适用于对较大的存放在外存储器(如硬盘)中的文件。
为减少外存访问次数,1970年R.bayer和E.maceright提出了一种适用于外部查找的树,其特点是插入、删除时易于保持平衡,外部查找效率高,适合于组织磁盘文件的动态索引结构,这就是将要讨论的B_树。
B_树是一种平衡的多路查找树,作为索引组织文件,用以提高访问速度。
一棵m阶的B_树,可以为空树,或者是一棵满足下列性质的m叉树:
(1)树中每个结点至多有m棵子树。
(2)若根结点不是叶子结点,则至少有两棵子树。
(3)除根结点之外所有非叶子结点至少有[m/2]棵子树。
(4)有s个子树的非叶子结点具有s-1个关键字,所有的非叶子结点中包含下列信息:(n,A0,K1,A1,K2,…,Kn,An),其中n为关键字个数,Ki(i=1,2,…,n)为关键字,Ki<Ki+1(i=1,2,…,n-1),Ai(i=1,2,…,n)为指向子树根结点的指针,且指针Ai-1所指子树中所有结点的关键字均小于Ki,An所指子树中所有结点的关键字均大于在这里插入图片描述

(5)B_树总是树高平衡的,所有的叶子结点都在同一层,且不包含任何关键字信息。通常叶子结点也被称为失败结点。失败结点并不存在,指向这些结点的指针都为空。引入失败结点是为了便于分析B_树的查找性能。
1. B_树的查找
B_树的查找类似二叉排序树的查找,所不同的是B_树每个结点上是多关键字的有序表,在达到某个结点时,先在有序表中查找,若找到,则查找成功;否则,到按照对应的指针信息指向的子树中去查找,当到达叶子结点时,则说明树中没有对应的关键字,查找失败。即在B_树上的查找过程是一个顺时针查找结点和在结点中查找关键字交叉进行的过程。
B_树的查找步骤如下:
Step 1:在当前结点中对关键字进行二分查找。
Step 2:如果查找到关键字key,则返回相关记录;如果当前结点是叶子结点,就返回检索失败信息;若无法找到关键字key,而存在两个关键字i、j使得i<key<j,则将位于i右边的第一个孩子结点作为当前结点,执行Step 1。
算法分析
B_树的查找是由两个基本操作交叉进行的过程,即:(1)在B_树上查找结点;(2)在结点中查找关键字。
B_树通常是存储在外存上,操作(1)就是通过指针在磁盘相对定位,将结点信息读入内存,之后再对结点中的关键字有序表进行顺序查找或折半查找。因为在磁盘上读取结点信息比在内存中进行关键字查找耗时多,所以在磁盘上读取结点信息的次数,即B_树的层次数是决定B_树查找效率的首要因素。
对含有n个关键字的m阶B_树,最坏情况下达到多深呢?可按照平衡二叉树进行类似分析。首先,讨论m阶B_树各层上的最少结点数。由B_树定义:第一层至少有1个结点;第二层至少有2个结点;由于除根结点外的每个非终端结点至少有[m/2]棵子树,则第三层至少有2[m/2]个结点;……;以此类推,第k+1层至少有个在这里插入图片描述结点,而k+1层的结点均为叶子结点。若一个m阶B_树有n个关键字,则其叶结点(即查找失败的结点)数量为n+1,因此可以得出:在这里插入图片描述,即:在这里插入图片描述。即可知,对于含有n个关键字的B_树进行查找时,从根结点到关键字所在结点的路径上进行比较的次数(即涉及的结点数)不超过在这里插入图片描述
2. B_树的插入
在B_树上插入关键字与在二叉排序树上插入结点不同,关键字的插入不是在叶结点上进行的,而是在最底层的某个非终端结点中添加一个关键字,若该结点上的关键字个数不超过m-1个,则可直接插入到该结点上;否则该结点上的关键字个数至少为m个,因而使该结点的子树超过了m棵,这与B_树的定义不符,所以要进行调整,即结点的“分裂”.
具体方法为:关键字加入结点后,将结点中的关键字分成三部分,使得前后两部分关键字个数均大于等于([m/2]-1),而中间部分只有一个结点。前后两部分成为两个结点,中间的一个结点将其插入到父结点中。若插入父结点而使父结点中关键字个数超过m-1,则父结点继续分裂,直到插入某个父结点,其关键字个数小于m。可见,B_树是自底向上生长的。
3. B_树的删除
关于B_树的删除分两种情况处理。
① 删除最底层结点中关键字。
(a)若结点中关键字个数大于等于[m/2],直接删去。
(b)否则,除余项与左兄弟(无左兄弟,则找右兄弟)项数之和大于等于2([m/2]-1),就与它们父结点中的有关项一起重新分配。
(c)若删除后,余项与左兄弟或右兄弟之和均小于2([m/2]-1)就将余项与左兄弟(无左兄弟,则与右兄弟)合并。由于两个结点合并后,父结点中的相关项不能保持,把相关项也并入合并项。若此时父结点被破坏,则继续调整,直到根结点。
② 删除为非底层结点中的关键字。
若所删除关键字是非底层结点中的Ki,则可以用指针Ai所指子树中的最小关键字X代替Ki,然后再删除该关键字X,直到这个结点X在最底层的结点上,即转换成①的情形。

8.3 哈希表

在查找时,如果给定值key在表中存在,则只需根据key并通过对应关系H,即可得到key在表中的存储位置H(key)。因此不需要进行比较,而是仅通过H(key)的计算,就能获得所要查找的记录。称这样的对应关系H为哈希(Hash)函数散列函数杂凑函数,而根据这个思想所建立的表称之为哈希表(Hash Table)或散列表杂凑表,根据哈希函数所得的储存位置称为哈希地址(Hash Address)或散列地址,而这种映像过程则被称为哈希造表散列
散列过程具体可描述如下:
(1)储存记录时,通过哈希函数计算记录的哈希地址,并按此哈希地址储存该记录。
(2)查找记录时,通过同样的哈希函数计算记录的哈希地址,并按此哈希地址访问该记录。
可见,散列既是一种储存方法,也是一种查找方法。但散列不是一种完整的储存结构,因为它只是通过记录的关键字定位该记录,很难完整地表达记录之间的逻辑关系,所以,散列主要是面向查找的储存结构。
在散列技术中,由于记录的定位主要基于哈希函数的计算,不需要进行关键字的多次比较。所以,一般情况下,散列技术的查找速度要比前面介绍的基于关键字比较的查找技术的查找速度高。但是,散列方法不适用于范围查找。
利用哈希函数进行查找有如下特点:
(1)与其他查找算法不同,哈希函数是利用函数的映射来实现查找。而哈希函数的形式并不是固定的,只需要映射的值均落在表长允许的范围内即可,因此对于不同的情况可以设计不同的哈希函数。
(2)根据函数的性质可知,对于某个函数y=f(x),不同的x通过f获得的y有可能是相同的。哈希函数同样有这样的问题,即不同的关键字有可能得到相同的哈希地址,称这种现象为冲突(Collision)。这些有相同函数值的关键字被称为该函数上的同义词(Synonym)当有关键字发生冲突时,是无法进行直接查找的。
如果按散列函数计算出的地址将记录加入散列表时产生了冲突,就必须另外再找一个存储空间来存放它,这就产生了如何处理冲突的问题。因此,采用散列技术需要考虑的如下两个主要问题。
(1)哈希函数的设计:如何设计一个合理的哈希函数。合理性主要体现在两个方面:一是函数应尽可能简单,以便提高计算速度;二是函数的计算结果(即地址)应尽量均匀分布在哈希地址空间中,以减少空间浪费。
(2)冲突的处理:如何采取合适的处理冲突的方法来解决冲突。

散列技术通过哈希函数建立了从记录的关键字集合到哈希表的地址集合的一个映射,而哈希函数的定义域是查找集合中全部记录的关键字,如果哈希表中有m个地址单元,则哈希函数的值域必须在0至m-1之间。
哈希函数的设计方法有很多种,一般来说,希望哈希函数能够把记录以相同的概率放置在哈希表的所有存储单元中。为了设计一个好的哈希函数,应当遵循下列两个原则:
(1)计算简单。哈希函数的计算量应该较小,否则会降低查找效率。
(2)每个关键字所对应的哈希地址分布均匀。函数值要尽量均匀分布在地址空间上,这样才能保证存储空间的有效利用,并且减少冲突。

在设计哈希函数时,要根据具体情况,选择一个较合理的方案。下面将讨论几种常见的哈希函数构造方法。

  1. 直接定址法直接定址法是定义一个线性函数,取关键字对于该函数的函数值作为哈希地址,即:
    在这里插入图片描述
    其中a和b为常数。通常而言,这类函数是一一对应函数,因此不会产生冲突,但要求地址集合与关键字集合的大小相同,并且当关键字跨度非常大时并不适用。因此该方法在实际生活中并不常用。
    2. 数字分析法
    在关键字集合中,若每个关键字均由m位组成,而每位上有r种不同的取值(如一位数字可以有0~9这10种取值,英文字母则有a~z这26种取值),通过分析r中不同符号在每一位上的分布情况,选择其中某几位分布较为均匀的符号组合成哈希地址。
    3. 平方取中法
    平方取中法是取关键字平方后的中间几位作为哈希地址,这是一种较为常见的哈希函数构造方法。通常在选定哈希函数时不一定能知道关键字的所有情况,且取其中几位作为哈希地址也不一定适合,因此平方可以使随机分布的关键字得到的哈希地址也随机,而其所取的地址位数则由表长决定。
    4. 折叠法
    折叠法是将关键字按位数分割成几部分(其中最后一部分的长度可能会较小),然后将这些部分按一定的方式进行求和,按哈希表表长取后几位作为哈希地址。
    通常折叠法有两种形式:位移法和间接叠加法。其中位移法是将各部分按最后一位对齐相加;间接叠加法是从一端向另一端沿分割界来回折叠,然后对齐相加。
    5. 除留余数法
    选择一个常数p,取关键字除以p所得的余数作为哈希地址,即:在这里插入图片描述
    该方法对于p的选取非常重要,若哈希表长度为m,则要求p小于等于m且接近m,并且一般选用质数作为p,或者是一个不包含小于20质因子的合数。除留余数法是一种最简单也最常见的哈希函数构造方法,它不仅可以对关键字直接取模,也可以在折叠法、平方取中法之后取模。

由于关键字的复杂性和随机性,很难有理想的哈希函数存在。在建立哈希表时,如果记录按哈希函数计算出的哈希地址发生了冲突,则必须另外找个存储空间来存放该记录,这便是所谓的处理冲突。冲突的处理方法有许多种,不同的方法可以得到不同的哈希表,下面介绍几种常用的处理冲突的方法。
1. 开放定址法
所谓开放定址,即是一旦根据关键字所得到的哈希地址发生了冲突(该地址已经存放了数据元素),则继续按某种规则寻找下一个空闲单元的哈希地址(通常将寻找下一个空闲单元的过程称为探测),只要哈希地址足够大,空的哈希地址总是能够找到的。其函数定义为:
在这里插入图片描述
其中,H为哈希函数,m为哈希表的表长,di为所取的增量序列。每种再散列方法的区别在于di的取值不同。寻找下一个空闲单元的哈希地址的方法较多,下面介绍3种比较常用的方法:线性探测再散列、二次探测再散列和伪随机探测再散列。
(1)线性探测再散列。线性探测再散列是取增量序列di为1,2,…,m-1的方法。其过程可描述为:当哈希地址i发生冲突时,查看哈希地址i+1是否为空,若为空则将数据放入,否则查看i+2是否为空,依次类推。
(2)二次探测再散列。二次探测再散列是取增量序列di为在这里插入图片描述的再散列方法。其过程可描述为:当哈希地址i发生冲突时,查看哈希地址i+1是否为空,若为空则将数据放入,否则查看i-1是否为空,若为空则将数据放入,否则查看i+22是否为空,依此类推。
(3)伪随机函数再散列。伪随机函数再散列是取增量序列di为一个伪随机数的再散列方法。其过程可描述为:当哈希地址i发生冲突时,产生一个伪随机数d1,查看哈希地址i+d1是否为空,若为空则将数据放入,否则重新产生一个伪随机数d2查看i+d2是否为空,以此类推。
2. 再哈希法
再哈希法用数学表达式可以描述为:在这里插入图片描述其中,RHi均为不同的哈希函数。再哈希法的本质是使用k个哈希函数,若第一个函数发生冲突,则利用第二个函数再生成一个地址,直到产生的地址不冲突为止。
3. 链地址法
链地址法是将每个哈希地址都作为一个指针,指向一个链表。若哈希表长为m,则建立m个空链表,将哈希函数对关键字进行转换为i后,映射到统一哈希地址i的同义词均加入到地址i所指向的链表中。
4. 建立一个公共溢出区
设哈希函数产生的哈希地址集为[0,m-1],则分配两个表。一个表为基本表,其每个存储单元仅存放一个数据元素;另一个表为溢出表,只要关键字对应的哈希地址在基本表上发生了冲突(即为同义词),则将发生冲突的元素一律放入该表中。
查找时,对于给定关键字key通过哈希函数计算出哈希地址为i,则先与基本表中地址为i的数据元素进行比较,若相等则查找成功;否则再在溢出表中进行查找。

  • 20
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值