1.前言
二分搜索是一种数据查找方法,可以在O(logn)
时间复杂度内定位一个元素的在有序序列中的位置。是查找有序序列元素的必备方法,同时也可以用来搜索区间边界,进而将有序区间一分为二。本文就简单的介绍一下二分查找的实现方法和应用场景。
2.二分查找的两种写法
二分查找的思想很简单,就是每迭代一次,将目标元素所在的区间范围缩小为原来的一半。
二分查找存在两种写法,根据区间的划分不同,分为闭区间和左闭右开区间两种,前者便于理解,但是后者具备了更多实用的特性。
方法一
这种方法的特征如下
- 初始条件:
l=0
,r=array.length-1
- 循环不变式:目标元素在区间
[l, r]
内 - 循环结束条件:
l>r
- 循环结束:此时目标元素所在区间为空,即目标元素
target
不在序列中
public int biSearch(int[] array,int target){
int l=0;
int r=array.length-1;
while(l<=r){
int m = l+(r-l)/2;
//判断中位元素和target的关系,缩小[l,r]区间
if(target == array[m]){
return m;
}
else if(target < array[m]){
r = m - 1;
}
else{
l = m + 1;
}
}
return -1;
}
循环会正确结束的证明:
- m是
[l,r]
中的元素 - 每一轮迭代
[l,r]
的距离一定会缩小1 - 所以最后循环条件
l<=r
一定会打破
方法二
这种方法的特征如下
- 初始条件:
- 将序列划分为三个区间
[0,l)
、[l,r)
和[r,array.length)
l=0
r=array.length
- 将序列划分为三个区间
- 循环不变式:
- 区间
[0,l)
内的元素<target
, - 区间
[r,array.length)
内的元素>=target
- 区间
- 循环结束条件:
l==r
- 循环结束:
- 由于
l==r
区间[l,r)
消失,只剩下区间[0,l)
和[r,array.length)
- 根据循环不变式:
l
为[l,array.length)
区间的第一个元素,也即序列中>=target
的第一个元素的索引 - 如果
array[l]!=target
,array[l]
就是>target
的最小元素 l
为target
在序列中的插入位置
- 由于
public int biSearch(int[] array,int target){
int l=0;
int r=array.length;
while(l<r){
int m = l+(r-l)/2;
else if(array[m]>=target){
r = m;
}
else{
l = m + 1;
}
}
return l;
}
循环会正确结束的证明:
m
是[l,r]
中的元素- 每一轮迭代
[l,r]
的距离一定会缩小1:- 只有当
r==m
时,不会缩小 - 因为
r==m
且m = l + (r-l)/2
- 所以
r = l + (r-l)/2
- 所以
r -l = (r-l)/2
- 所以
r -l == 0
- 只有当
r - l==0
时,可能存在死循环,这和循环条件l<r
矛盾
- 只有当
- 所以最后循环条件
l<r
一定会打破
3.总结
- 方法1只适用于查找单个元素是否在序列中
- 方法2可以看作一种寻找区间边界的方法,具有普适性:
- 定义两个区间
[0,l)
和[r,array.length)
然后不断缩小这两个区间的边界[l,r)
, - 直到
l==r
,就找到了这两个区间的真正边界
- 定义两个区间
4.参考
《编程珠玑》