二分查找,最基本的查找算法之一,根据我的面试经验,能够真正写出bug free的二分查找的人,连1/10的人都不到。
下面我们就来剖析一下各种二分查找,希望读了此文以后,配合练习,都可以写出bug free的代码。
前提条件,待排序数组从小到大有序。二分查找的核心思想,有点类似分治思想,每次将给定数字与中间元素对比,若相等直接返回索引;若比中间元素大,则在后半部分区间继续查找;若比中间元素小,则在左半部分继续查找;查找区间一直缩半,直到找到目标数字或查找区间为0。
1.常规二分查找,数组中无重复数字。查找等于给定值的数字。
有了基本思想,标准二分查找就很好写了。
//非递归方法
public static int binarySearch(int[] array, int value) {
if(array == null || array.length == 0) return -1;
int low = 0, high = array.length - 1; //high必须是length - 1
while (low <= high) { //必须要有等于
int mid = (low + high) >>> 1; //int mid = low + ((high - low) >> 1)写出这样不会有溢出的情况。
if (value == array[mid]) return mid;
if (value > array[mid]) low = mid + 1;
else high = mid - 1;
}
return -1;
}
//递归方法
public static int binarySearch(int[] array, int length, int value) {
if(length == 0) return -1;
return binarySearchRecur(array, 0, length, value);
}
private static int binarySearchRecur(int[] array, int low, int high, int value) {
if (low > high) return -1;
int mid = (low + high) >>> 1; //int mid = low + ((high - low) >> 1)写出这样不会有溢出的情况。
if (array[mid] == value) {
return mid;
} else if (array[mid] > value) {
return binarySearchRecur(array, low, mid - 1, value);
} else {
return binarySearchRecur(array, low + 1, high, value);
}
}
public static void main(String[] args) {
int[] data = {0, 1, 3, 5};
int index = binarySearch(data, data.length - 1, 5);
System.out.println(index);
}
2.变体二分查找,数组中有重复数字,查找第一个等于给定值的数字。
这种情况,其实我们只要稍微处理一下找到相等数字时的情况即可,如果找到给定数字,且该数字是数组首元素或该数字的上一个数字与给定数字不等,那这个数字就是我们要找的;如果不是,那就继续缩小区间,在前半部分查找,贴一下代码,你就明白了。
public static int binarySearchFirstEqual(int[] array, int value) {
if(array == null || array.length == 0) {
return -1;
}
int low = 0, high = array.length - 1; //high必须是length - 1
while (low <= high) { //必须要有等于
int mid = (low + high) >>> 1; //int mid = low + ((high - low) >> 1)写出这样不会有溢出的情况。
if (value == array[mid]) { //重点关注这部分代码
if (mid == 0 || array[mid -1] != value) {
return mid;
} else {
high = mid - 1;
}
}
else if (value > array[mid]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] data = {0, 1, 3, 3, 3, 3, 5};
int index = binarySearchFirstEqual(data, 3);
System.out.println(index);
}
3.变体二分查找,数组中有重复数字,查找最后一个等于给定值的数字。
这种情况与2类似,如果找到给定值的数字,判断该数字是不是数组的最后一个元素或数组的下一个位置元素与给定值不等,那么我们就找到了最后一个等于给定值的数字了;否则,在该数字的后半部分区间继续寻找。
public static int binarySearchLastEqual(int[] array, int value) {
if(array == null || array.length == 0) {
return -1;
}
int length = array.length - 1;
int low = 0, high = array.length - 1; //high必须是length - 1
while (low <= high) { //必须要有等于
int mid = (low + high) >>> 1; //int mid = low + ((high - low) >> 1)写出这样不会有溢出的情况。
if (value == array[mid]) {
if (mid == length || array[mid + 1] != value) {
return mid;
} else {
low = mid + 1;
}
}
else if (value > array[mid]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] data = {0, 1, 3, 3, 3, 3, 5};
int index = binarySearchLastEqual(data, 3);
System.out.println(index);
}
4.变体二分查找,数组中有重复数字,查找第一个大于等于给定值的数字。
有了前面两种变体,再考虑这个问题,其实就比较简单了,如果找到了一个大于等于给定值的数字,判断该位置是不是数组首元素或该位置的上一个位置元素小于给定值,那么此位置的数字就是我们要找的。否则的话,继续在左半区间继续寻找。
public static int binarySearchFirstGreatEqual(int[] array, int value) {
if(array == null || array.length == 0) {
return -1;
}
int low = 0, high = array.length - 1; //high必须是length - 1
while (low <= high) { //必须要有等于
int mid = (low + high) >>> 1; //int mid = low + ((high - low) >> 1)写出这样不会有溢出的情况。
if (array[mid] >= value) {
if (mid == 0 || array[mid -1] < value) {
return mid;
} else {
high = mid - 1;
}
} else {
low = mid + 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] data = {0, 1, 3, 5, 8, 10, 12};
int index = binarySearchFirstGreatEqual(data, 7);
System.out.println(index);
}
5.变体二分查找,数组中有重复数字,查找最后一个小于等于给定值的数字。
这个变体与3类似,当找到符合条件的数字时,判断是不是数组的最后位置或该位置的下一位置数字大于给定值,那么这就是我们要寻找的数字,否则的话,继续在右半区间寻找。
public static int binarySearchLastLessEqual(int[] array, int value) {
if(array == null || array.length == 0) {
return -1;
}
int length = array.length - 1;
int low = 0, high = array.length - 1; //high必须是length - 1
while (low <= high) { //必须要有等于
int mid = (low + high) >>> 1; //int mid = low + ((high - low) >> 1)写出这样不会有溢出的情况。
if (array[mid] <= value) {
if (mid == length || array[mid + 1] > value) {
return mid;
} else {
low = mid + 1;
}
} else {
high = mid - 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] data = {0, 1, 3, 5, 8, 10, 12};
int index = binarySearchLastLessEqual(data, 7);
System.out.println(index);
}
欢迎关注我的微信公众号