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;
}
};