1.概念:二分查找也称为 “折半查找”,采用的是二分思想,每次可以将数据查询的范围缩小为原来的一半,所以它的效率非常高。
同时二分查找针对的也是有序的数据集合,它的时间复杂度为O(logn)。
2.O(logn)是一个非常恐怖的数量级,即使n非常大,但是对应的logn的结果也是非常的小。2^32次方大约等于42亿,但是我们从42亿个数据中利用二分查找来搜索数据,也就查找32次就够了。
3.O(logn)有时候甚至比常量及的时间复杂度O(1)还要高效,因为再计算时间复杂度时,我们会忽略掉低阶,常数等,所以O(1)可能指O(100),O(10000),它有时比O(logn)效率低。
4.简单的二分查找实现:
/* 简单的二分查找算法:使用二分查找的前提:序列必须是有序的*/
public class BinarySearch {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,0,7,6,54,3,7,34,36,98};
QuickSort sort = new QuickSort();
sort.quickSrotInternally(arr,0,arr.length-1);
BinarySearch binarySearch = new BinarySearch();
int pivot = binarySearch.binarySearch(arr,98);
System.out.println(pivot);
}
public int binarySearch(int[] arr,int target) {
if(arr == null) return -1;
int low = 0;
int high = arr.length - 1;
/*一定是low <= high , 因为如果是low<high,那么可能某一些元素会搜索不到*/
while (low <= high) {
/*如果是int pivot = (low + high) / 2; 其实是不准确的。因为如果low和high是特别大的
* 32位的数据,那么low+high有可能会出现溢出的问题。
* 如果需要将性能优化到极致,那么可以low + ((high-low)>>1); 因为移位运算比除法更快*/
int pivot = low + (high-low)/2;
if (target == arr[pivot]) {
return arr[pivot];
} else if (target < arr[pivot]) {
/*这里一定是pivot-1或者pivot+1,如果要是写成hight=pivot,那么某些情况下可能会出现死循环。*/
high = pivot - 1;
}else {
low = pivot + 1;
}
}
return -2;
}
}
5.二分查找的局限性:
(1)二分查找依赖的是顺序表结构,简单点来说就是数组,即使链表也是顺序表结构,但是二分查找算法需要利用下标随机访问元素,链表不支持随机访问,所以不可以。
(2)需要进行二分查找的数据,必须是有序数据,如果数据杂乱无章,那么需要先排好顺序。
(3)数据量太小的不适合二分查找:因为数据量太小,利用顺序遍历就可以了,二分查找也显现不出来优势。但是如果数据之间的比较操作比较耗时,那么尽管数据量非常小,也建议使用二分查找。例如:在String类型的数组中查找某一个字符串,每一个字符串的长度非常长,这时使用遍历逐一比较非常耗时。
(4)数据量特别大也不适合二分查找:因为二分查找依赖数组,数组在内存中需要连续的存储空间,如果待查找的数据大小为1GB,那么找到一块连续的1GB空间有些吃力。
6.二分查找的变体:
(1)在序列中查找第一个与目标值相等的元素:
/*二分查找变体一:查找第一个等于给定值的元素。*/ public int bSearchFirst(int[] arr,int target) { if(arr == null) return -1; int low = 0; int high = arr.length-1; while (low <= high) { int mid = low + ((high-low)>>1); /*如果中间值大于target,那么要查找的值一定在左边*/ if(arr[mid] > target) { high = mid - 1; }else if(arr[mid] < target) {/*如果中间值小于target值,那么要查找的值一定在右边*/ low = mid + 1; }else { /*如果当前数组下标为0,或者当前元素的前一个元素,比target值小,那么当前值就是第一个要找的值*/ if(mid == 0 || arr[mid - 1] < target) { System.out.println("第一个等于目标值的索引:"+mid); return arr[mid]; }else { high = mid - 1; } } } return -2; }
(2)在序列中查找第一个大于等于给定值的元素:
/*变体二:查找第一个大于等于给定值的元素*/ public int bSearchLast(int[] arr,int target) { if(arr == null) return -1; int low = 0; int high = arr.length - 1; while (low <= high) { int mid= low + ((high-low) >> 1); if(arr[mid] < target) { low = mid + 1; }else { /*如果当前元素下标为0,或者当前元素的前一个元素值小于目标元素,那么当前元素为要查找的值*/ if(mid == 0 || arr[mid - 1] < target) { System.out.println("第一个大于等于给定元素的下标:"+mid); return arr[mid]; }else { high = mid - 1; } } } return -2; }