目录
透彻理解二分查找
二分查找其实用了分治的思想,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题直到最后子问题可以简单地直接求解。二分查找就是将中间结果与目标进行比较,比较一次便去掉一半范围,直到找到目标元素。
一 循环实现二分查找
我们可以使用循环来让区间中间值与目标值进行比较,若区间中间值大于目标值,则接下去查找该区间的左半部分,否则则查找右半部分,不断切分下去,直到区间中间值等于目标值,则找到目标值索引。
注!这里为了防止low和high过大导致low+high溢出,因此我们可以这么写
int mid = low + ((high - low) >> 1)
注!(high - low) >> 1 外层一定要加括号,不然就变成(low + (high - low) >> 1)了,这显然不是我们要的中间值,还可能出现死循环。
具体实现代码如下:
/**
* 循环实现二分查找
*
* @param array 查找数组
* @param low 最小索引
* @param high 最大索引
* @param target 查找数
* @return 目标数索引
*/
public static int binarySearch1(int[] array, int low, int high, int target) {
while (low <= high) {
// 从数组某部分的中间开始查找
int mid = low + ((high - low) >> 1);
// 如果查找到则返回该索引
if (array[mid] == target) return mid;
// 如果目标数小于中间值,则查找左半部分
else if (target < array[mid]) high = mid - 1;
// 如果目标数大于中间值,则查找右半部分
else low = mid + 1;
}
return -1;
}
二 递归实现二分查找
能用循环自然也可以用递归啦,实现思路一样,只需把循环判断及其不同情况的结果换成递归语句递归进行判断就可以啦。实现代码如下:
/**
* 递归实现二分查找
*
* @param array 查找数组
* @param low 最小索引
* @param high 最大索引
* @param target 查找数
* @return 目标数索引
*/
public static int binarySearch2(int[] array, int low, int high, int target) {
if (low <= high) {
// 从数组某部分的中间开始查找
int mid = low + ((high - low) >> 1);
// 如果查找到则返回该索引
if (array[mid] == target) return mid;
// 如果目标数小于中间值,则查找左半部分
else if (target < array[mid]) return binarySearch2(array, low, mid - 1, target);
// 如果目标数大于中间值,则查找右半部分
else return binarySearch2(array, mid + 1, high, target);
}
return -1;
}
三 元素中有重复的二分查找
假如在上面的有序数组的基础上,元素存在重复,如果重复则找左侧第一个,那有该如何解决呢。其实前面判断的思路都不用改变,只需在最后找到目标值时做一点小小的处理即可。
1)若当前查找索引的值等于目标值且不为最左端,则往左移直到值不为目标值,则最后该索引+1则为目标值的最左一个
2)若当前查找索引已在最左端,且等于目标值,则该索引即为寻找的目标值位置
实现代码如下:
/**
* 有重复的二分查找
*
* @param array 查找数组
* @param target 查找数
* @return 目标数索引
*/
public static int binarySearch3(int[] array, int target) {
if(array == null || array.length == 0) return -1;
int low = 0;
int high = array.length;
while (low <= high) {
// 从数组某部分的中间开始查找
int mid = low + ((high - low) >> 1);
// 如果目标数小于中间值,则查找左半部分
if (target < array[mid]) high = mid - 1;
// 如果目标数大于中间值,则查找右半部分
else if(target > array[mid]) low = mid + 1;
// 如果查找到则返回该索引
else {
// 查找到元素后如果当前元素为目标元素则循环向左移,知道最左一位的左边一位
while (mid != 0 && array[mid] == target) mid--;
// 如果此时移到最左边而且为目标值,则返回索引0
if (mid == 0 && array[mid] == target) return mid;
// 不在最左边,则返回当前索引+1,即为最左边重复元素的索引
return mid + 1;
}
}
return -1;
}