适用场景
适用于有序数组中查找某一个值.
每查找一次,就将搜寻范围缩小一半,
平均时间复杂度是O(logN), 根据换底公式, 简记作:O(lgN).
主要难点
主要难点在于边界条件的判断;
大致思路:
1.当供查找的数组不合法时,直接返回结果,查询无果;
2.当数组长度等于1时,直接判断是否相等即可;
3.当数组仅有两个元素时,直接分别判断;
4.当数组元素多于2个时, 采用如下策略:
1) 有限比较数组头尾两个元素与查询目标元素的值比较, 因为是有序数组,如果在界外,则直接判断给出查询结果;
2)如果和边界元素比较后,范围在界内,则开始真正的二分查找法,分别采用low, high, mid三个游标来界定查询范围.
实现方式
1.迭代法
2.递归法
3.Arrays.binarySearch(int[] arr, int target) API
实践代码如下:
import java.util.Arrays;
/**
* 默认有序数组arr[]是从小到大的顺序排列的,如果不是则需要先结合排序算法使用.
*/
public class BinarySearch {
// 迭代法
public int binarySearch1(int[] arr, int target) {
// 先判断数据的有效性
if (arr == null || arr.length < 1) {
return -1;
}
int low = 0;
final int length = arr.length;
int high = length - 1;
int mid;
// System.out.println("length:" + length + ",low:" + low + ",high:" + high
// + ",mid:" + (low + high) / 2);
// 先判断头尾游标的值
if (length == 1) {
if (arr[0] == target) {
return 0;
} else {
return -1;// not found
}
} else { // length >= 2
if (arr[low] == target) {
return low;
} else if (arr[high] == target) {
return high;
} else if (arr[low] > target || arr[high] < target) {
//如果待查找的数值在有序数组最大或者最小值之外,直接判查询未果,无须再二分查找了.
return -1;
}
}
while (low < high) {
mid = (low + high) / 2;
// System.out.println("low:" + low
// + ",value[low]:" + arr[low]
// + ",high:" + high + ",value[high]:" + arr[high]
// + ",mid:" + mid + ",value[mid]:" + arr[mid]);
//这段非常重要,否则将可能出现死循环,
//当头游标和尾游标的中间值已经是起始或者末尾两个游标位之一时,代表查找结束,且无果.
if (high == mid || low == mid) {
return -1;
}
if (arr[mid] < target) {
low = mid;
} else if (arr[mid] > target) {
high = mid;
} else if (arr[mid] == target) {
return mid;
}
}
return -1;
}
// 递归法
public int binarySearch2(int[] arr, int target) {
// System.out.println("enter binarySearch2");
if (null == arr || arr.length < 1) {
return -1;
}
final int length = arr.length;
if (length == 1) {
if (arr[0] == target) {
return 0;
} else {
return -1;// not found
}
} else { // length >= 2
if (arr[0] == target) {
return 0;
} else if (arr[arr.length - 1] == target) {
return arr.length - 1;
} else if (arr[0] > target || arr[length - 1] < target) {
return -1;// not found
}
}
return binarySearchRecursion(arr, 0, arr.length - 1, target);
}
private int binarySearchRecursion(int arr[], int low, int high, int target) {
// System.out.println("length:" + arr.length + ",low:" + low + ",high:" + high
// + ",mid:" + (low + high) / 2);
int mid;
while (low < high) {
mid = (low + high) / 2;
//这段非常重要,否则将可能出现死循环,
//当头游标和尾游标的中间值已经是起始或者末尾两个游标位之一时,代表查找结束,且无果.
if (mid == low || mid == high) {
return -1;
}
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
return binarySearchRecursion(arr, mid, high, target);
} else if (arr[mid] > target) {
return binarySearchRecursion(arr, low, mid, target);
}
}
return -1;
}
// Arrays.binarySearch API法
public int binarySearchArrays(int[] arr, int target) {
if (arr == null || arr.length <= 0) {
return -1;
}
//Arrays.sort(arr);
return Arrays.binarySearch(arr, target);
}
public static void main(String[] args) {
// 多个测试用例数组
int[] arr = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,};
int[] arr2 = {
1, 2, 4,};
int[] arr3 = {
1, 2, 4, 6
};
int[] arr4 = {
1, 2, 4, 6, 8
};
int[] bigArr = new int[10000];
for (int i = 0; i < 10000; i++) {
bigArr[i] = i * 2;
}
System.out.println("init arr done!");
int wanted = 6;
int[] toBeSearchArr = bigArr;
BinarySearch bs = new BinarySearch();
System.out.println(
"binearySearch1 :" + bs.binarySearch1(toBeSearchArr, wanted));
System.out.println(
"binearySearch2 recursion :" + bs.binarySearch2(toBeSearchArr, wanted));
System.out.println("Arrays.binarySearch():" + bs.binarySearchArrays(toBeSearchArr, wanted));
}
}
测试结果如下:
init arr done!
binearySearch1 :3
binearySearch2 recursion :3
Arrays.binarySearch():3
[Done] exited with code=0 in 1.508 seconds
Arrays.binarySearch API算法实现如下:
根据OpenJDK11源码网站:https://hg.openjdk.org/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/util/Arrays.java
// code from https://hg.openjdk.org/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/util/Arrays.java
public static int binarySearch(int[] a, int key) {
return binarySearch0(a, 0, a.length, key);
}
public static int binarySearch(int[] a, int fromIndex, int toIndex,
int key) {
rangeCheck(a.length, fromIndex, toIndex);
return binarySearch0(a, fromIndex, toIndex, key);
}
// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
//System.out.println("low:" + low + ",high:" + high + ",mid:" + mid);
if (midVal < key) {
low = mid + 1;
} else if (midVal > key) {
high = mid - 1;
} else {
return mid; // key found
}
}
return -(low + 1); // key not found.
}
/**
* Checks that {@code fromIndex} and {@code toIndex} are in the range and
* throws an exception if they aren't.
*/
static void rangeCheck(int arrayLength, int fromIndex, int toIndex) {
if (fromIndex > toIndex) {
throw new IllegalArgumentException(
"fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
}
if (fromIndex < 0) {
throw new ArrayIndexOutOfBoundsException(fromIndex);
}
if (toIndex > arrayLength) {
throw new ArrayIndexOutOfBoundsException(toIndex);
}
}
从上可知, JDK实现中,除了判断数组的有效性之外,边界并未做特殊处理,举例:
当target/key元素小于第一个元素或者大于最后一个元素时,需要跑logN(N>3)次才可以得到查询无果的消息, 而上边自写方案做了特殊处理的,直接判断一次即可.
另外还有个差异点是: Arrays的实现当查询无果时,返回的值,并非统一的-1, 而是-(low+1).
1) 小于第一个元素
int[] bigArr = new int[100000];
for (int i = 0; i < 100000; i++) {
bigArr[i] = i * 2;
}
System.out.println("init arr done!");
int wanted = -100002;// 小于第一个元素,Arrays需要查询17次才可以得到结果.
int[] toBeSearchArr = bigArr;
BinarySearch bs = new BinarySearch();
long startTime = System.currentTimeMillis();
int result = -1;
//for (int j = 0; j < 1000000; j++) {
result = bs.binarySearchArrays(toBeSearchArr, wanted);
//}
long timeUsed = System.currentTimeMillis() - startTime;
System.out.println("binarySearchArrays search " + wanted + ",result: " + result + " -> using " + timeUsed + "ms");
long startTime1 = System.currentTimeMillis();
int result1;
//for (int j = 0; j < 1000000; j++) {
result1 = bs.binarySearch2(toBeSearchArr, wanted);
//}
long timeUsed1 = System.currentTimeMillis() - startTime1;
System.out.println("binarySearch2 search " + wanted + ",result: " + result1 + " -> using " + timeUsed1 + "ms");
输出结果:
2) 大于最后一个元素
int wanted = 200002;// 7, 6, 8
int[] toBeSearchArr = bigArr;
输出结果: