二分查找与分治
1、分治的概念
在计算机科学中,分治法是一种很重要的算法。字面解释是“分而治之”,就是把一个复杂的问题分解成两个或者更多的相同或相似的子问题,再把子问题分成更小的子问题······直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如二分搜索、排序算法(快速排序,归并排序)等等······
2、二分查找
任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易求解,解题所需的计算时间也越少。要想直接解决一个规模较大的问题,有时是相当困难的。
二分查找就是将中间结果与目标进行比较,一次去掉一半,因此二分查找可以说是最简单、最典型的分治算法。
分治和递归是一回事吗?
这是两种完全不同的思想,二分查找是分治思想,我们可以使用递归或者循环的方式来做。而很多递归问题也不一定是分治的,因此两个完全不是一回事。
2.1、循环解决二分查找
代码如下:
public int binarySearch(int[] array, int low, int high, int target) {
while (low <= high) {
int mid = (low + high) / 2;
if (array[mid] == target) {
return mid;
} else if (array[mid] > target) {
//由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
high = mid - 1;
} else {
//由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
low = mid + 1;
}
}
return -1;
}
对于上述的代码,还有可以优化的地方,其中有一个很重要的问题没有处理。
在计算机中,除的效率非常低,一般可以用移位来代替,也就是
将:
int mid = (low + high) / 2;
换成:
int mid = (low + high) >> 1;
除此之外,还有一个问题,假如low和high很大的话,low + high可能会溢出。因此我们可以这么写:
int mid = low + ((high - low) >> 1)
为什么要加上这个括号呢?是因为移位运算符>>优先级比加减要低,所以如果不加括号的话,low + (high - low) >> 1的实际结构其实是这样的:(low + (high - low)) >> 1。很明显不是我们希望的结果,需要再加个括号更换运算顺序。
最终的代码如下:
public int binarySearch(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 (array[mid] > target) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return -1;
}
2.2、递归解决二次查找
递归解决就是将待查的目标值与中间值对比,如果相等,则mid就是我们需要的值,如果mid > target,则继续在左半边调用二分查找继续找,如果mid < target,则就在右半边调用二分查找继续找,直到得到最终结果。
代码如下:
public int binarySearch (int[] array, int low, int high, int target) {
//递归终止条件
if (low <= high) {
int mid = low + ((high - low) >> 1);
if (array[mid] == target) {
return mid; //返回目标值的位置,从1开始
} else if (array[mid] > target) {
//由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
return binarySearch(array, low, mid - 1, target);
} else {
//由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
return binarySearch(array, mid + 1, high, target);
}
}
return -1; //表示没有搜到
}
2.3、元素中有重复的二分查找
假如在上述的基础上,元素存在重复,如果重复则找左侧第一个,该怎么做呢?
这里的关键是找到目标结果之后不是返回而是继续向左侧移动,最简单的方法就是找到相等位置向左使用线性查找,直到找到相应的位置。
public int search(int[] nums, int target) {
if (nums == null || nums.length == 0)
return -1;
int left = 0;
if (nums[0] == target) {
return 0;
}
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else {
//找到之后,往左边找
while (mid != 0 && nums[mid] == target)
mid--;
return mid + 1;
}
}
return -1;
}