一. 问题描述:
就是一个有序数组,给定一个数字,存在的话返回下标,不存在的话返回-1;
二. 两种递归实现对比:
这道题首先应该想到的就是递归实现。
我这里所提到的两种递归实现其实就是把数组分段——一种是分为左闭右开的形式,另一种是两边都闭的形式。
· 左闭右开
int BinarySearch(vector<int> &nums, int l, int r, int target) {
// 不是你想象地那么完美
if (l == r) return -1;
int m = l + (r - l) / 2; // 防止溢出
// 建议还是把这个判断放在最前面
if (target == nums[m]) return m;
else if (target > nums[m]) return BinarySearch(nums, m + 1, r, target);
else if (target < nums[m]) return BinarySearch(nums, l, m, target);
}
- 优势:判断终止的时候特别方便,两个指针碰到一起,就结束了。
- 劣势:我们左闭右开其实是把一个[a, b)的区间能过完美地分为[a, c), [c, b)这两个部分。然而这里并派不上用场——因为我还要挖掉中间一个元素,这就看上去很不和谐。
· 左右都闭
int BinarySearch(vector<int> &a, int l, int r, int target) {
if (l > r) return -1;
int m = l + (r - l) / 2; // 防止溢出
if (target == a[m]) return m;
else if (target > a[m]) return BS(a, m + 1, r, target);
else if (target < a[m]) return BS(a, l, m - 1, target);
}
我思前想后,发现这种写法没什么劣势。m + 1和m - 1充分体现了挖掉中间这个元素的处理。
唯独要注意的是l == r的时候可能还要进行处理。
三. 非递归实现
递归实现简单地令人发指。于是我们就寻思着有无非递归的实现。
大家看到这时一个尾递归,所以我们无需引入栈。只要相应的循环即可。
实现也特别简单,我这里就使用之前觉得没什么缺陷的左闭右闭来实现了。
int BinarySearch(vector<int> &a, int target) {
// 左闭右也闭的形式
int l = 0;
int r = a.size() - 1;
while (l <= r) {
int m = l + (r - l) / 2; // 防止溢出
if (target == a[m]) return m;
else if (target > a[m]) l = m + 1;
else if (target < a[m]) r = m - 1;
}
return -1;
}
四. 其它经验
- 注意把中间的元素挖掉。
- 利用l + (r - l) / 2防止加法溢出。