数据结构——四十、折半查找(王道408)
文章目录
- 前言
- 一.算法思想
- 1.思路
- 2.举例
- 1.查找成功
- 2.查找失败
- 二.代码实现
- 三.查找效率分析
- 1.分析过程
- 2.结论
- 四.折半查找判定树的特性
- 1.右子树结点数-左子树结点数=0或1
- 1.分析过程
- 2.结论
- 3.题目
- 2.折半查找的判定树一定是平衡二叉树
- 3.折半查找的判定树一定是二叉排序树
- 五.折半查找的查找效率
- 六.知识回顾与重要考点
- 结语
前言
本文介绍了折半查找(二分查找)的基本思想、实现方法和查找效率分析。折半查找仅适用于有序顺序表,通过不断缩小查找区间来定位目标元素。文章通过示例演示了查找成功和失败的流程,并提供了升序和降序排列时的C语言实现代码。在效率分析部分,通过构建判定树计算了成功和失败情况下的平均查找长度(ASL),指出折半查找判定树具有右子树结点数-左子树结点数=0或1的特性,且一定是平衡二叉树和二叉排序树。最后说明元素个数为n时,判定树高度为⌈log₂(n+1)⌉(不含失败结点)或⌈log₂(n+1)⌉+1(含失败结点)。
一.算法思想
1.思路
- 折半查找,又称“二分查找”,仅适用于有序的顺序表。
- 当前检查的这个元素,把目前有可能出现目标元素的这一整个区间一分为二,分为了左边的区间和右边的区间,根据我们想要找的这个关键字和当前被检查元素的这个大小关系,我们就可以知道我们到底应该是在左边还是右边来查找
- 循环上述操作,在不停的缩小区间后,如果元素存在
2.举例
1.查找成功

- 假设我们现在要在这个有序的顺序表当中找到33这个元素,那首先我们会用两个指针,low和high来分别指向我们目前要搜索的这个区间范围

- 第一轮我们要检查的元素是low和high他们中间的一个元素,我们用一个指针mid=(low+high)/2来指向它

- 因此第一个检查的元素是29,子现在对比mid的所指向的元素,还有我们的目标关键字33会发现我们要查找的目标要大于当前mid的所指向的这个元素,所以如果33这个元素存在的话,那么一定是在mid的所指位置的右边这个部分,因此我们要做的就是把low这个指针指向6这个位置

- 同样是检查 low和high这个区域内的中间那个元素mid,这个元素的值是37,37是要大于33的,所以如果33存在的话,那么肯定是在mid的所指位置的左边这个区域

- 因此接下来我们会让high指针指向7这个位置

- 接下来操作也是一样,mid指针指向6,32<33,因此在mid的右边寻找,因此接下来我们会让low这个指针指向mid的右边那个位置

- 同样的,此时mid指向7,33=33,恰好就是我们要查找的目标
2.查找失败

- 其他步骤和之前一样,只是最后一步的判断条件略有不同

- 此时检查的就是10这个元素,那由于我们要找的目标12依然是大于10的,所以如果12存在的话,那只有可能是在mid的所指的右边那个区间内,所以和刚才的处理逻辑一样,我们会让lowi这个值等于mid+1

- 可以看到low>high,因此查找失败
二.代码实现
typedef struct{//查找表的数据结构(顺序表)Element *elem;//动态数组基址int TableLen;//表的长度
}SSTable;
//折半查找(元素升序排列)
int Binary_Search(SSTable L, ElemType 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; //从前半部分继续查找elselow=mid+1; //从后半部分继续查找}return -1; //查找失败,返回-1
}
//折半查找(元素降序排列)
int Binary_Search(SSTable L, ElemType 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; //从前半部分继续查找elselow=mid+1; //从后半部分继续查找}return -1; //查找失败,返回-1
}
三.查找效率分析
1.分析过程

-
如果我们要查找的关键字刚好是29的话,只需要经过一次关键字对比

-
我们要查找的关键字小于(大于)29的话,通过第二次的关键字对比,我们可以找到的元素应该是13(37)

-
那如果依然不是这两个元素,那接下来也是一样的

-
此时我们有可能会检查的元素有7,16,32,41

-
如果查了这几个元素还没有找到目标的话,接下来进行之前一样的操作

-
所以对于刚开始给出的这个查找表,在查找成功的情况下,我们最多有可能进行4轮查找,这是查找成功的情况
-
而对于查找失败的情况,再补上失败节点就可以

-
也就是说我们要查找的关键字如果落在这紫色方框内的某一个区间内,那么最终我们肯定是查找失败的
2.结论
- A S L 成功 = ( 1 ∗ 1 + 2 ∗ 2 + 3 ∗ 4 + 4 ∗ 4 ) / 11 = 3 ASL_\text{成功} = (1*1+2*2+3*4+4*4)/11 = 3 ASL成功=(1∗1+2∗2+3∗4+4∗4)/11=3
- A S L 失败 = ( 3 ∗ 4 + 4 ∗ 8 ) / 12 = 11 / 3 ASL_\text{失败}=(3*4+4*8)/12=11/3 ASL失败=(3∗4+4∗8)/12=11/3
四.折半查找判定树的特性
1.右子树结点数-左子树结点数=0或1
1.分析过程
-
如果当前low和high之间有奇数个元素,则mid分隔后左右两部分元素个数相等

-
如果当前low和high之间有偶数个元素,则mid分隔后左半部分比右半部分少一个元素

2.结论
- 折半查找的判定树中,若 m i d = ⌊ ( l o w + + h i g h ) / 2 ⌋ mid=\lfloor(low++high)/2\rfloor mid=⌊(low++high)/2⌋,则对于任何一个结点,必有:
右子树结点数-左子树结点数=0或1
3.题目
- 练习:若 m i d = ⌊ ( l o w + + h i g h ) / 2 ⌋ mid=\lfloor(low++high)/2\rfloor mid=⌊(low++high)/2⌋,画出含1个元素、2个元素、3个元素…16个元素的查找表对应的折半查找判定树,注:暂不考虑失败结点(Key:右子树结点数-左子树结点数=0或1)
解:1.一个元素:

2.两个元素,由于右子树结点只可能比左子树更多,因此:
3.三个元素,由于右子树最多比左子树多一个结点,因此:
4.十六个元素,按照之前的规律依次往后推演



注:图中数字只是一个编号,并不是关键字的值
一点小规律:从根节点出发,编号的顺序是左孩子的左孩子编号->最左边的堂兄弟结点的左孩子的左孩子编号->兄弟结点的左孩子的左孩子编号->最左边的堂兄弟结点最近的兄弟结点的左孩子的左孩子编号->…(以此类推)
左孩子编完后编右孩子,右孩子的编号顺序也是和左孩子一样的
2.折半查找的判定树一定是平衡二叉树
- 任何一个节点的左子树和右子树的深度之差都不会超过1
- 折半查找的判定树中,只有最下面一层是不满的因此,元素个数为n时树高 h = ⌈ log 2 ( n + 1 ) ⌉ h=\left\lceil\log_2(n+1)\right\rceil h=⌈log2(n+1)⌉(和完全二叉树相同)(不包含失败结点,如果包含失败结点,则其树高 h = ⌈ log 2 ( n + 1 ) ⌉ + 1 h=\left\lceil\log_2(n+1)\right\rceil+1 h=⌈log2(n+1)⌉+1)
3.折半查找的判定树一定是二叉排序树
- 判定树结点关键字:左<中<右,满足二叉排序树的定义
- 失败结点:n+1个(等于成功结点的空链域数量)
五.折半查找的查找效率
- 查找成功的ASL≤h(树的高度)
- 查找失败的ASL≤h(树的高度)
- 折半查找的时间复杂度 = O ( log 2 n ) O(\log_{2}n) O(log2n)
六.知识回顾与重要考点

- 拓展思考
折半查找时间复杂度=O(log₂n)
顺序查找的时间复杂度=O(n)
那么,折半查找的速度一定比顺序查找更快?
答:不一定,只是平均查找速度更快

如果 m i d = ⌈ ( l o w + h i g h ) / 2 ⌉ mid=\lceil(low+high)/2\rceil mid=⌈(low+high)/2⌉
那么,判定树是什么样子?
答:

如果当前low和high之间有奇数个元素,则mid分隔后,左右两部分元素个数相等

如果当前low和high之间有偶数个元素,则mid分隔后,左半部分比右半部分多一个元素

结语
二更,最近事情比较多,更新会有延迟😖
如果想查看更多章节,请点击:一、数据结构专栏导航页
