二分查找算法:一种针对有序数据集合的查找算法,也叫折半查找算法。
思考题:
假设有1000万个整数数据,每个数据占8字节,如何设计数据结构和算法,快速判断某个整数是否出现在这1000万数据中?希望实现这个功能所需要的内存空间不超过100M。
1. 理解二分查找思想
二分查找是非常简单易懂的快速查找算法,在生活中也是比较常见。举一个猜字游戏为例,进行说明。随机写一个0-99的数字,猜写的到底是哪个数。假设我写的是23,大概会按照下面这个步骤进行猜测:
实际的开发场景:
假设这里有1000条订单数据,已经按照订单金额从小到大排序了,每个订单金额都不同,并且最小单位是元。我们现在想知道是否存在订单金额为19元的订单。如果存在,就返回订单数据,如果不存在则返回null。
传统的方法可能就是依次遍历这1000条数据,直到找到金额等于19元的订单为止。但是这样的查找会比较慢,最坏情况下,可能需要遍历1000条记录才能找到。那用二分查找能不能快速解决这个问题呢?
为了方便,这里假设只有10个订单,订单金额分别为:8,11,19,23,27,33,45,55,67,98。利用二分查找思想,每次都与区间的中间数据比较大小,缩小查找的范围。参看下图进行理解,其中low和high表示待查找区间的下标,mid表示待查找区间的中间元素的下标。
总结: 二分查找针对的是一个有序的数据集合,查找思想有点类似于分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或是区间被缩小为0。
2. 二分查找的时间复杂度
假设数据的大小是n,每次查找数据都会缩小为原来的一半,最坏的情况下,直到查找空间被缩小为空,才停止。
其中时,k值就是总共缩小的次数,而每一次缩小操作都只涉及两个数据的大小比较,所以经过k次区间缩小操作,时间复杂度是O(k)。由于,可以求得,所以时间复杂度是O(logn)。
这种对数时间复杂度,是一种极其高效的时间复杂度,有时候甚至会比复杂度为O(1)的算法还要高效。比如n等于2的32次方,大约42亿,也就是说42亿数据用二分查找只需要比较32次。而O(1)有可能会表示一个非常大的常量数据,所以有时候算法的执行效率没有O(logn)高。
3. 二分查找的递归和非递归实现
最简单的二分查找情况:有序数组中不存在重复元素。下面是一个最简单的二分查找算法的Java代码:
public int bsearch(int[] a, int n, int value){
int low = 0;
int high = n-1;
while(low <= high){
int mid = (low + high)/2;
if (a[mid] == value){
return mid;
} else if {a[mid] < value){
low = mid + 1;
} else {
high = mid - 1;
}
}
return -1;
}
容易出错的三个地方:
- 循环退出条件:low<=high,而不是low<high。
- mid取值。mid = (low+high)/2这种写法有问题,low与high较大时,和可能会溢出。改进方法写成:low+(high-low)/2。
- low和high的更新。low = mid+1,high = mid-1。如果直接写成是low = mid,high = mid,可能会导致死循环。
二分查找的递归实现:
//二分查找的递归实现
public int bsearch(int[] a, int n, int value){
return bsearchInternally(a, 0,n-1,value);
}
public int bsearchInternally(int[] a, int low, int high, int value){
if (low > high) return -1;
int mid = low + ((high - low)>>1);
if (a[mid] == value){
return mid;
} else if (a[mid] < value){
return bsearchInternally(a, mid+1, high, value);
} else {
return bsearchInternally(a, low, mid-1, value);
}
}
4. 二分查找的应用局限性
- 二分查找依赖的是顺序表结构,简单点说就是数组(二分查找是根据下标随机访问元素)
- 二分查找针对的是有序数据
- 二分查找不太适合数据量太小的查找(数据量小的时候,二分查找跟顺序遍历差不多)
- 二分查找不适合数据量太大的查找(数据量大的时候,数据很难用数组进行存储,所以就不适合用二分查找)
5. 解答思考题
将数据存储在数组中,内存占用差不多80M,符合内存限制要求。将这1000万数据从小到大排序,然后再利用二分查找算法,就可以快速地查找到想要的数据了。