二分查找(折半查找)

1.前言

一直以来觉得自己写算法都是毫无章法,随心所欲,导致当真的要用起来时无从下手,所以想通过阅读其它优秀博主的文章来学习常用模板。

2.基础篇

这部分,我就照搬百度了,因为看了下百度上的介绍感觉已经十分的详细了(和书上差不多)。

2.1查找过程

  • 首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较;
  • 如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表。
  • 如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。
  • 重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

2.2算法要求

1.必须采用顺序存储结构
2.必须按关键字大小有序排列。

3.进阶篇

这些部分我会随着自己的经历逐渐更新,先暂时根据自己的认知来进行补充吧。

3.1 注意项1-中间数选择

其实,二分查找的思路浅显易懂,但写代码以及写题目的时候却经常会产生疑问,而这些疑问甚至我现在还没有很好的处理。

首先,是对中间数的取舍,比如说如果是奇数位的数组,这我很容易理解,如果用right和left代表两端的下标,那中间数就是(right+left)/2,但是如果是数组的位数是偶数位,那就要稍微考虑下了,到底是取前面的还是后面的呢?

这在我最近的看的代码里面,以及自己的基本认知中,都是取前面的数,有可能大家对我说的前面,后面不太理解。这里我举个例子就是下标0和下标5,这里有6个数,中间的数的下标为2和3,那么此时取2还是3呢?这就是我说的前面后面,最初的时候我思考了很多,反正习惯上都是取2而不是3。

3.2 注意项2-端点取舍

其次,我思考的注意点是对折半后,端点是否可以舍去,因为舍去和不舍去算法都是正确的,因为经过折半处理,这个端点已经确认不是我要找的那个数,那么舍去也是理所当然的,但是不舍去也没有错误。这在程序实现的时候感觉没有多大的区别,但是期末时候写题目的时候就十分的纠结了。

但通常来说是不舍去,还是原来6位数组这个例子,判断下标2发现比下标2小,那么新的两端就是0和2,而不是0和1。

3.3 注意项3-整数溢出

然后还一个思考点就是整数溢出问题,因为极有一个可能,我们是在一个巨大的数组中来寻找一个数,而且很不巧的是,寻找的数值正好在最右侧。那么就有很大的可能产生整数溢出,从而使得结果错误。关于整数溢出的问题,其实也是代码的常见漏洞之一,下次别人让我举例子,我就举这个例子了。

常见的漏洞写法就是mid=(left+right)/2,这样很容易产生问题,因此改进的写法是mid=left+(right-left)/2;这样就尽量避免了整数溢出问题。

3.4 注意项4-区间选择

二分查找其实可以根据区间的不同,演变出多种写法,虽然本质是一样的,但是还是存在区别。而且因为区间的选择,速度可能会有所不同,当然这只是个人的猜想,而且也无关紧要。

常见的区间选择是闭区间,也是考试常考的方法,这个建议考试的时候全部以这个为标准吧。

还一种是左闭右开区间,这种本质上是差不多,但是在后面新区间的选择上会有所不同,这个在后面会有所提及。

不推荐:自己思考的一种是左开右开(闭)区间,这样就可以把端点舍去,但是这种方法目前我还没看到有人使用,而且这样有一个致命的弱点就是开始的下标0要被囊括,如果把left=-1的话,万一使用者用的是无符号整型,那就是另一个溢出问题了。

3.5 C++模板

leetcode 35题就是专门讲二分查找(虽然也可以用其它的查找,但二分查找方便且速度快)

这个算法和我前面的中间数选择有点小冲突,但代码是没有问题的,我只能说,一个简单的算法会因人而异,但考试有时候只是限制了思维。

它的算法逻辑有点特殊,还是假设一个6位的数组,那么它定义数组使用的左闭右开的区间,就像这样[0,6),和我们[0.5]本质是一样的,但这样,它的中间数很容易得到是3而我们只会得到2,这就是区别之处。因为使用的数学区间有所不同,所以后面的处理也有所不同。

假如使用的左闭右开区间,因为要遵照不舍去端点的原则,如果是在右侧,新区间直接就是[mid,right)。如果是左侧,为了包含端点,新区间为[left,mid+1)。

假如用的是闭区间,并且在左侧,就是[left,mid],右侧就是[mid+1,right]。

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0;
        int right = n; // 我们定义target在左闭右开的区间里,[left, right)  
        while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间
            int middle = left + ((right - left) >> 1);
            if (nums[middle] > target) {
                right = middle; // target 在左区间,因为是左闭右开的区间,nums[middle]一定不是我们的目标值,所以right = middle,在[left, middle)中继续寻找目标值
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,在 [middle+1, right)中
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值的情况,直接返回下标
            }
        }
        return right;
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

此人受打击,决定去力扣历练

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值