数据结构——查找的基本概念
——本节内容为Bilibili王道考研《数据结构》P68~71视频内容笔记。
目录
一、基本概念
1.查找
在数据集合中寻找满足某种条件的数据元素的过程称为查找。
2.查找表(查找结构)
用于查找的数据集合称为查找表,它由同一类型的数据元素(或记录)组成。
3.关键字
数据元素中唯一标识该元素的某个数据项的值,使用基于关键字的查找,查找结果应该是唯一的。
4.静态查找表
只进行查找符合条件的数据元素的操作,无需动态地修改查找表的查找表称为静态查找表。
5.动态查找表
需要动态地插入或删除的查找表称为动态查找表。
6.查找长度
在查找运算中,需要对比关键字的次数称为查找长度。
7.平均查找长度ASL
所有查找过程中进行关键字的比较次数的平均值,其数学定义为
式中,n是查找表的长度即元素个数;Pi是查找第i个数据元素的概率,一般认为每个数据元素的查找概率相等,即Pi=1/n;Ci是查找到第i个数据元素所需要进行的比较次数即查找第i个元素的查找长度。
ASL的数量级反映了查找算法时间复杂度。
8.查找算法的效率评价
(1)平均查找长度ASL;
(2)通常考虑查找成功、查找失败两种情况下的ASL。
二、顺序查找
1.顺序查找
即从头到尾依次查找,不论怎么优化,其时间复杂度都为O(n)。
2.顺序查找的实现
typedef struct { //查找表的数据结构
int* elem; //元素存储空间基址,建表时按实际长度分配,0号单元留空
int TableLen; //表的长度
}SSTable;
int Search_Seq(SSTable ST, int key) {
ST.elem[0] = key; //“哨兵”,将关键字key放入0号位置
int i = ST.TableLen;
for (int i = ST.TableLen; ST.elem[i] != key; --i); //从后往前找
return i; //若表中不存在关键字为key的元素,将查找到i为0时退出for循环
}
3.图示
4.补充解释
(1)上述算法中,将ST.elem[0]称为哨兵,引入它的目的是使得Search_Seq内的循环不必判断数组是否会越界;
(2)当每个元素的查找概率相等为1/n时,有:
成功情况下:
失败情况下:
5.有序表的顺序查找
(1)引出:如果在查找之前就已经知道关键字是有序的,则查找失败时可以不用再比较到表的另一端就能返回查找失败的信息,从而降低顺序查找失败的平均查找长度。
(2)可以用如图所示的判定树来描述有序线性表的查找过程,树中的圆形结点表示有序线性表中存在的元素,树中的矩形结点称为失败结点(若有n个结点,则相应地有n+1个查找失败节点)。
(3)一个成功结点的查找长度=自身所在层数;
一个失败结点的查找长度=其父结点所在层数;
默认情况下,各种失败情况或成功情况都等概率发生。
6.被查概率不相等的情况
将被查概率大的放在靠前的位置(仅可降低查找成功的查找长度,对于查找失败来讲毫无优化),如下图:
三、折半查找
1.折半查找
折半查找又称二分查找,它仅适用于有序的顺序表。
2.思想
首先将给定值key与表中中间位置的元素比较,若相等,则查找成功,返回该元素的存储位置;若不等,则所需查找的元素只能在中间元素以外的前半部分或后半部分(例如,在查找表升序排序时,若给定值key大于中间元素,则所查找的元素只可能在后半部分)。然后再缩小的范围内继续进行同样的查找,如此重复,直到找到为止,或确定表中没有所需要查找的元素,则查找不成功,返回查找失败的信息。
3.算法
升序顺序表的折半查找:
typedef struct { //查找表的数据结构
int* elem; //元素存储空间基址,建表时按实际长度分配,0号单元留空
int TableLen; //表的长度
}SSTable;
int Binary_Search(SSTable L, int key) { //升序顺序表折半查找
int low = 0, high = L.TableLen - 1, mid;
while (low <= high)
{
mid = (low + high) / 2; //取中间位置
if (L.elem[mid] == key)
return mid; //查找成功则返回所在位置
else if (L.elem[mid] > key)
high = mid - 1; //从前半部分继续查找
else
low = mid + 1; //从后半部分继续查找
}
return -1; //查找失败,返回-1
}
4.例子
(1)查找成功
(2)查找失败
5.查找效率分析
(1)通过折半查找判定树来分析查找效率:
(2)折半查找判定树的构造
①如果当前low和high之间有奇数个元素,则mid分隔后左右两部分元素个数相等;
②如果当前low和high之间有偶数个元素,则mid分隔后左半部分比右半部分少一个元素;
③得出结论:在折半查找判定树的构造中,若,则对于任何一个结点,必有:右子树结点数-左子树结点数=0或1;
④折半查找判定树一定是一个平衡二叉树,任何一个树的左右子树的深度之差都不会超过1;因此元素个数为n时树高;
⑤判定树结点关键字:左<中<右,满足二叉排序树的定义;失败结点:n+1个(等于成功结点的空链域数量)
(3)树高,所以查找成功ASL<=h,查找失败ASL<=h,所以折半查找的时间复杂度=
。
四、分块查找
1.分块查找
分块查找又称索引顺序查找,它吸取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找。对于整个顺序表来说,块内无序,块间有序;而对于索引表来说,其中保存每个分块的最大关键字和分块的存储区间,如下图所示:
typedef struct { //索引表
int maxValue;
int low, high;
}Index;
int List[100]; //顺序表存储实际元素
2.查找步骤
(1)在索引表中确定待查记录所在的块,可以顺序查找或折半查找索引表;
(2)在块内顺序查找。
3.折半查找索引表
(1)由于索引表是有序的,所以可以对索引表进行折半查找;
(2)若索引表中不包含目标关键字,则折半查找索引表最终停在low>high,并且此时我们要在low所指的分块中进一步查找。
(3)关于为什么在low中进一步查找:
(4)若在折半查找索引表时,low>high时low超出了索引表的范围,此时直接查找失败,因为low>high时我们要在low所指的分块中进一步查找,但low指空了。
4.查找效率分析