第九章 查找
另一种在实际应用中大量使用的数据结构--查找表。
所谓查找,即为在一个含有众多的数据元素的查找表中找出某个“特定的”数据元素。
查找表 search table 是由同一类型的数据元素构成的集合。集合中的数据元素之间存在着完全松散的关系,故查找表非常灵便。
对查找表经常进行的操作:
>查询某个元素是否在查找表中;
>检索某个元素的各种属性;
>插入
>删除
静态查找表 static search table:只包含查询和检索。
动态查找表 dynamic search table:包含查询、检索、插入、删除。
关键字 Key 用于标识数据元素。
>主关键字 primary key:唯一的标识一个记录。
>次关键字 secondary key:识别若干记录。
查找 searching:
根据给定的某个值,在查找表中确定一个其关键字等于给定值的记录。
由于表中数据元素之间仅存在“同属一个集合”的松散关系,需在数据元素之间人为的加上一些关系,以便按某种规则进行查找,即以另一种数据结构来表示查找表。
>静态查找表
>动态查找表
9.1 静态查找表
静态查找表有不同的表示方法,在不同的表示方法中,实现查找操作的方法也不同。
9.1.1 顺序表的查找
以顺序表或线性链表表示静态查找表,则可用顺序查找来实现。
顺序查找 sequential search:
从表中最后一个记录开始,逐个进行记录关键字和给定值的比较,若相等,则查找成功;反之,直到第一个记录都不相等,则表明表中没有要查找的记录,查找不成功。
Tip:在查找尾端设置哨兵,而免去每一步都要检测整个表是否查找完毕,可节约一半的时间。哨兵的值等于所查找的关键字。
查找表性能度量:平均查找长度(Average Search Length)ASL
对记录的查找概率不相等的查找表,若能预先得知每个记录的查找概率,则应先对记录的查找概率进行排序,使表中记录按查找概率由小到大重新排列,以便提高查找效率。
在一般情况下,记录的查找概率预先无法测定。
为了提高查找效率,可以在每个记录中附设一个访问频度域,并使顺序表中的记录保持按访问频度非递减有序排列,使得查找概率大的记录在查找过程中不断后移,以便在以后的逐次查找中减少比较次数。
或者在每次查找之后都将刚查找到的记录直接移至表尾。
顺序查找:
缺点:平均查找长度较长,当n很大时,查找效率低。
优点:简单且适应面广,对表结构无任何要求,无论记录是否按关键字有序均可应用。
9.1.2 有序表的查找
折半查找 binary search:
先确定待查记录所在的范围(区间),然后逐步缩小范围直到找到或找不到该记录为止。
折半查找性能:
对任意的n,当n>50时,有下列近似结果
折半查找只适用于有序表,且限于顺序存储结构,对线性链表无法有效地进行折半查找。
Fibonacci查找:
根据Fibonacci序列的特点对表进行分割。
开始时,表中记录个数比某个Fibonacci数小1,即
Fibonacci查找的平均性能比折半查找好,但最坏情况下的性能(虽然仍是O(logn))比折半查找差。
另一个优点,分割时只需进行加减运算。
插值查找:
根据给定值key来确定进行比较的关键字位置i的查找方案。
公式:
其中,high和low为最大关键字和最小关键字的下标。
插值查找只适合于关键字均匀分布的表。在这种情况下,对表长较大的顺序表,其平均性能比折半查找好。
9.1.3 静态树表的查找
当有序表中各记录的查找概率不等时,折半查找性能未必最优。
描述查找过程的判定树为何类二叉树时,其查找性能最佳?
如果只考虑查找成功的情况,则查找性能最佳的判定树是其带权内路径长度之和PH值取最小值的二叉树。
称PH值最小的二叉树为静态最优查找树(Static Optimal Search Tree)。
构造静态最优查找树花费的时间代价高。
静态树表是把有序的静态查找表根据数据被查找的概率生成一棵二叉树。
介绍一种构造近似最优查找树的有效算法。
次优查找树(Nearly Optimal Search Tree):带权内路径长度PH值在所有具有同样权值的二叉树中近似为最小。
方法:选取第i个记录作为根结点,然后分别对左右序列构造两个次优查找树作为i的左右子树。
i满足的条件:左序列权值和与右序列权值之和差值的绝对值最小。
由于在构造次优查找树的过程中,没有考察单个关键字的相应权值,则有可能出现被选为根的关键字的权值比与它相邻的关键字的权值小。此时应作适当调整:选取邻近的权值较大的关键字作次优查找树的根结点。
例:关键字 A B C D E
权值 1 30 2 29 3
大量的实验研究表明,次优查找树和最优查找树的查找性能之差仅为1%~2%,很少超过3%。
且构造次优查找树的算法的时间复杂度为O(nlogn)。
9.1.4 索引顺序表
有点像Skip List。
分块查找又称索引顺序查找,这是顺序查找的一种改进方法。
除表本身外,还需建立一个“索引表”。
索引表按关键字有序,表或者有序或者分块有序。
分块查找过程需分两步进行:先确定待查记录所在的块(子表),然后在块中顺序查找。
由于索引表按关键字有序,则确定块的查找可以用顺序查找,亦可用折半查找。而块中记录是任意排列的,则在块中只能是顺序查找。
分块查找是顺序查找和折半查找的简单结合。
若都用顺序,。
9.2 动态查找表
表结构支持插入和删除操作。
9.2.1 二叉排序树和平衡二叉树
二叉排序树(Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点均小于根结点;
(2)若右子树不空,则右子树上所有结点均大于根结点;
(3)左右子树均为二叉排序树。
二叉排序树又称二叉查找树。
中序遍历二叉排序树可得到一个关键字的有序序列。
即一个无序序列可以通过构造一棵二叉排序树而变成一个有序序列,构造树的过程即为对无序序列进行排序的过程。
插入:每次插入的新结点都是二叉排序树上新的叶子结点。
删除:
- 若删除点无孩子结点,直接删除;
- 若删除点只有一个孩子,则将其孩子替代其位置;
- 若删除点有两个孩子,两种方法:
a) 链上左孩子,右孩子为左孩子的最右;或链上右孩子,左孩子为右孩子最左;
b) 用直接前驱替代,然后删掉直接前驱;或用直接后继替代,然后删掉直接后继。
平衡二叉树(Balanced Binary Tree 或 Height-Balanced Tree)或者是一棵空树,或者是具有如下性质的二叉树:
它的左右子树都是平衡二叉树,且左右子树的深度之差的绝对值不超过1。
平衡因子BF(Balance Factor):该结点左子树的深度减去右子树的深度。
平衡二叉树所有结点的平衡因子只可能是-1,0,1。
只要二叉树上有一个结点平衡因子的绝对值大于1,则该二叉树就是不平衡的。
在平衡二叉排序树BBST上插入一个新的数据元素e的递归算法如下:
- 若BBST为空树,则插入一个数据元素为e的新结点作为BBST的根结点,树的深度增1;
- 若e的关键字和BBST的根结点的关键字相等,则不进行插入;
- 若e的关键字小于BBST的根结点的关键字,而且在BBST的左子树中不存在和e有相同关键字的结点,则将e插入在BBST的左子树上,并且当插入之后的左子树深度加1时,分别就下列不同情况处理之:
a) BBST的根结点的平衡因子为-1,则将根结点的平衡因子更改为0,BBST的深度不变;
b) BBST的根结点的平衡因子为0,则将根结点的平衡因子更改为1,BBST的深度增1;
c) BBST的根结点的平衡因子为1:
若BBST的左子树根结点的平衡因子为1,则需进行单向右旋平衡处理,并且在右旋处理之后,将根结点和其右子树根结点的平衡因子更改为0,树的深度不变;
若BBST的左子树根结点的平衡因子为-1,则需进行先向左、后向右的双向旋转平衡处理,并且在旋转处理之后,修改根结点和其左右子树根结点的平衡因子,树的深度不变;
- 若e的关键字大于BBST的根结点的关键字,而且在BBST的右子树中不存在和e有相同关键字的结点,则将e插入在BBST的右子树上,并且当插入之后的右子树深度增加1时,分别就不同情况处理之。其处理操作和步骤3中所述相对称。
LL型:新结点Y插入到A的左子树的左子树上 左左->右
RR型:新结点Y插入到A的右子树的右子树上 右右->左
LR型:新结点Y插入到A的左子树的右子树上 左右->左右
RL型:新结点Y插入到A的右子树的左子树上 右左->右左
AVL树的平衡化处理:通过调换根结点,使左右子树的深度相等。
在一棵AVL树上插入结点可能会破坏树的平衡性,需要平衡化处理恢复平衡,且保持BST的结构性质。
若用Y表示新插入的结点,A表示离新插入结点Y最近的,且平衡因子变为2的祖先结点。
可用4种旋转进行平衡化处理:
>LL型:新结点Y插入到A的左子树的左子树上 左左->右
>RR型:新结点Y插入到A的右子树的右子树上 右右->左
>LR型:新结点Y插入到A的左子树的右子树上 左右->左右
>RL型:新结点Y插入到A的右子树的左子树上 右左->右左
4种不平衡情况经调整都有如下特性:
- 成为A型,调整前的中值结点成为新的根结点,其平衡因子为0,其左、右孩子分别是小值、大值结点;
- 调整后,树的深度和插入结点前一样。
特性2说明,如果插入一个结点导致平衡二叉树失衡,则只需在通向根结点的路径上进行一次平衡调整。
9.2.2 B-树和B+树
高为h的B-树的最大结点数:
设m阶B-树的高为h,失败结点位于h+1层,则关键字个数N最少到达多少?
1层1结点
2层2结点
3层结点
h层结点
若树中关键字有N个,则失败结点数为N+1。
即:
给定m和N可以求出最大高度:m=199,N=1,999,999,可得h<=4;
给定m和h可以求出最少关键字数:m=3,h=4,N>=15。
m的选择:查找关键字所用时间最少。
两部分时间:从磁盘读入结点所需时间,在结点中查找关键字所需时间。
在B-树上进行查找的过程是一个顺指针查找结点和在结点的关键字中进行查找交叉进行的过程。
在含有N个关键字的B-树上进行查找时,从根节点到关键字所在结点的路径上涉及的结点数不超过
插入过程为自底向上分裂结点。
每次插入首先在最低层的某个非终端结点中添加一个关键字,如果该结点的关键字个数不超过m-1,则插入完成;否则分裂。
B树索引,关键字后跟的是文件地址信息。
删除,若不是最后一层非终端结点,则将其用左子树的最右或者右子树的最左来替换。然后删除用于替换的结点。
否则分4种情况:
1、最后一层非终端结点,且为根结点,直接删;
2、最后一层非终端结点,不为根结点。
B-树主要用作文件的索引,因此它的查找涉及外存的存取。
查找包括两步:1、在B-树中找结点(磁盘);2、在结点中找关键字(内存)。
即,在磁盘上找到结点后,将其读入内存,再在内存中进行关键字的查找。
B+树:B-树的一种变形。
差异:
1、含k个关键字的结点必有k个子树;
2、非终端结点仅具有索引作用,与记录有关的信息均存放在叶结点中;
3、叶结点依关键字自小而大顺序链接。
两个头指针:一个指向根结点,另一个指向关键字最小的叶子结点。
两种查找方式:
1、沿着根结点随机查找;
2、从最小关键字结点顺序查找。
9.2.3 键树
键树又称数字查找树(Digital Search Tree):
它是一棵度大于等于2的树,树中的每个结点只含有组成关键字的符号。
若关键字是数值,则结点中只包含一个数位;若关键字是单词,则结点中只包含一个字母字符。
两种存储方式:1、孩子兄弟链;2、多重链表。
9.3 哈希表
9.3.1 什么是哈希表
前述查找结构中,关键字和相对存储位置是随机的,在查找过程中需要进行比较,是建立在比较基础上。
冲突(collision):哈希值相同。
同义词(synonym):哈希值冲突的关键字。
一般情况下,冲突只能尽可能减少,而不能完全避免。
哈希表:根据设定的哈希函数H(key)和处理冲突的方法将一组关键字映像到一个有限的连续的地址集(区间)上,并以关键字在地址集中的像作为记录在表中的存储位置,这种表便称为哈希表。
这一映像过程称为散列,所得存储位置称为哈希地址或散列地址。
9.3.2 哈希函数的构造方法
好的哈希函数:对于关键字集合,经哈希函数映像到地址集合中任何一个地址的概率是相等的,则称此类哈希函数为均匀的(Uniform)哈希函数。
即使关键字经过哈希函数得到一个“随机的地址”,以便使一组关键字的哈希地址均匀分布在整个地址区间中,从而减少冲突。
1、直接定址法
取关键字或关键字的某个线性函数值为哈希地址:
2、除留余数法
9.3.3 处理冲突的方法
1、开放定址法
2、再哈希法
对冲突的关键字用另一个哈希函数计算地址,直至不再冲突。
3、链地址法
4、建立一个公共溢出区
将冲突的关键字全部加入这个缓冲区中。