题目:
以下三种算法都没有问题!!!
针对一些细节进行优化或者不同的边界
基础版
/*
* 二分查找基础版
* param: 待查找有序数组arr[]
* target-待查找的目标值
* return:
* 查找到了就返回索引,否则返回-1
* */
public static int binarySearch(int[] arr, int target){
int i = 0; //左指针
int j = arr.length-1; //右指针
while (i <= j){
//int mid = (i+j)/2;
int mid = (i+j) <<< 1;
if(target < arr[mid]){ //目标在左边
j = mid - 1;
}
else if (arr[mid] < target){ //目标在右边
i = mid + 1;
}
else{
return mid; //找到了
}
}
// 没查找到
return -1;
}
这里有几个问题:
-
i<=j
还是i<j
?哪个对?区别在哪?
-
(i+j)/2
还是(i+j) <<< 1
?哪个对?为什么不行?
-
- 为什么都用小于符号?
答:
1、这里的i<=j
意味着区间内还有未比较的元素。如果判定条件是i<j
,存在当mid=i=j
时,会跳出while循环。
2、
针对特殊情况,解释一下为什么不用(i+j)/2
使用**右移操作<<< 1
**后(等效于除以2,向下取整)
3.都用小于符号,便于阅读代码,同时思路清晰,明了。
改进版
注意看改动处就行。主要是边界不一样,而且右指针所指向的数不参与比较。
//改进版,注意改动的地方
public static int binarySearch(int[] arr, int target){
int i = 0;
int j = arr.length; //第一处改
// 此时的右指针所指向的数不参与比较,只作为边界,但i指针所指向的数参与比较
while (i < j){ //第二处改
int mid = (i + j) >>> 1;
if (target < arr[mid]){
j = mid; //第三处改
}
else if (target > arr[mid]){
i = mid + 1;
}
else {
return mid;
}
}
return -1;
}
平衡版
针对基础版的特殊情况,当查找最左边的元素,此时假设while循环了 L 次,那么此时while里面比较了 L 次;但当查找最右边的元素,此时同样假设while循环了 L 次,那么此时while里面却比较了 2L 次。这就不平衡了。为了平衡点,见以下代码:
// 平衡版
public static int binarySearch(int[] arr, int target){
int i = 0;
int j = arr.length;
// 左闭右开区间,i指向的可能是目标,j指向的不是目标
while (1 < j - i){ // 不在循环内找出,等范围只剩下1时,退出循环,在循环外比较arr[i]和target
// 循环内的平均比较次数减少了
int mid = (i + j) >>> 1;
if (target < arr[mid]){
j = mid;
}
else {
i = mid; // 这里不用+1,有可能arr[mid]正是target,循环内只是比较了小于情况
}
}
if(arr[i] == target){
return i;
}
return -1;
}
}
总结
-
第一个算法代码,两边左右指针所指向的数可能参与比较。
-
第二个算法代码,其右指针是作为边界指针,所指向的数不参与比较,但左指针所指向的数可能参与比较。
-
第三个算法代码,主要是减少比较次数。