二分查找与分治
在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
这个技巧是很多高效算法的基础,如二分搜索、排序算法(快速排序,归并排序)等等……
任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。
二分查找就是将中间结果与目标进行比较,一次去掉一半,因此二分查找可以说是最简单、最典型的分治了。
二分查找,不管是循环还是递归方式,我觉得应该达到写到闭着眼睛,一分钟就能写出来的地步。
这里再补充一个问题, 分治和递归是一回事吗?很显然不是。这是两种完全不同的思想 ,二分查找是分支思想,我们可以使用递归或者循环的方式来做。而很多递归问题也不一定是分治的,因此两个完全不是一回事。
循环的方式
在计算机中,除的效率非常低,一般可以使用移位来代替
public int search(int[] nums, int target) {
int low = 0;
int high = nums.length - 1;
while (low <= high) {
int mid = low + ((high - low) >> 1);
if (nums[mid] == target) {
return mid;
}else if (nums[mid] > target) {
// 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
high = mid - 1;
}else {
// 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
low = mid + 1;
}
}
return -1;
}
递归的方式
public int search2(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 search2(array, low, mid-1, target);
}else{
// 由于array[mid]不是目标值,因此再次递归搜索时,可以将其排除
return search2(array, mid+1, high, target);
}
}
return -1; //表示没有搜索到
}
元素中有重复的二分查找
这里的关键是找到目标结果之后不是返回而是继续向左侧移动。第一种,也是最简单的方式,找到相等位置向左使用线性查找,直到找到相应的位置。
public static int search(int[] nums, int target) {
if (nums == null || nums.length == 0)
return -1;
int left = 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--;
if (mid == 0 && nums[mid] == target) {
return mid;
}
return mid + 1;
}
}
return -1;
}