1.介绍
二分查找也称折半查找(Binary Search),它是查找算法中较为基础的一种算法,是学习算法入门的好例子,也是一种效率较高的查找方法。但是,二分查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。对原始数据的结构要求较高
2.实现方法及其分析
1.二分查找(基础版)
public static int BinarySearch(int[] arr,int target){
int i = 0;int j = arr.length - 1; //定义两个指针分别指向数组的两端
while (i <= j){ //当两个指针之间还有元素时表示还没找完
int m = (i+j) >>> 1;//(i+j)/2; //每次取得两个指针中间的元素,并分别与目标元素进行比较(推荐使用无符号右移位运算 >>> 1 )
if(arr[m] < target){ //若目标元素大,则起始指针指向中间结点+1的地方,下次查找只需查找后半部分
i = m+1;
} else if (arr[m] > target) {//若目标元素小,则末尾指针指向中间结点-1的地方,下次查找只需查找前半部分
j = m-1;
}else{
return m; //若中间节点的值刚好等于目标元素的值,则直接返回该index
}
}
在上述的二分查找基础版方法中,我们先定义了两个“指针”,分别指向数组的前后两端。
然后在while循环中求出两个指针的中间位置(小数向下取整),再比较target与该位置元素的大小
从而决定缩小左边界还是右边界,循环往复,直至刚好与target值相等返回对应的数组下标即为与target元素相等的数组元素的位置,若找不到该元素则返回-1。
2.二分查找(左闭右开版)
public static int BinarySearch(int []arr,int target){
int i = 0;
int j = arr.length; //改动一
while (i < j){ //改动二
int m = (i + j) >>> 1;
if (arr[m] < target){
i = m; //改动三
} else if (target < arr[m]) {
j = m - 1;
} else if (target == arr[m]) {
return m;
}
}
return -1;
}
该算法在上述的基础版的基础上做了三个改动,使右侧指针指向的元素不可能为目标元素(相当于集合的开区间)
3.二分查找(平衡版)
public static int BinarySearch(int[] arr,int target){
int i = 0;
int j = arr.length;
while (1 < (j - i)){
int m = (i + j) >>> 1;
if(target < arr[m]){
j = m;
}else {
i = m;
}
if(target == arr[i]){
return i; //出循环的时候两个指针之间没有元素,直接比较i对应的元素(j只是边界不参与比较),有的话i对应的就是结果,无的话直接不成立,最后返回-1
}
}
return -1;
}
在上述的两种二分查找的实现方式中,我们不难发现,在while循环中的if语句比较中“<”和“>”的比较总是有先后顺序,这导致了当我们查找的target元素在数组中的位置偏左或偏右时执行的比较次数差异较大,总有一方的比较次数较快/较慢,为了解决这种不平衡,设计了二分查找的平衡版本
在平衡版本中,target==目标元素的比较语句被放在了while循环之外,这样循环内的“<”,“>”比较可以用if-else语句来执行(只需要一次比较),因此target元素无论在数组的哪边所需要进行的比较都是相近的,但同时由于 target==目标元素的比较语句被放在了while循环之外,所以循环每次都会被执行满,不会提前跳出整体运行速度一般。
3.Java中提供的二分查找API
在Java中内置了二分查找的API,在
Arrays.binarySearch(int [] arr;int target);中
查看其源码
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];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found.
}
观察发现其使用的是二分查找的基础版,有差别的是当需要找的元素不存在时返回 -(*插入点 - 1)。
*插入点:在该点插入target元素,其整个数组保持有序