递增整数序列的二分详解
二分搜索是一种时间复杂为log2n的算法,可以用于单调函数求根和单调序列查询的有效算法,即使数列长度高达10^9 也只需二分31次,查询速度接近常数,同时二分思想是一种很基础很重要的思想希望同学们都能掌握;
单调数列(单调递增)通用二分模板
简洁版
完全版
当start_index =0,end_index=len-1时,功能和简洁版一样;
1.
为啥不能写下面这句
if(mid==n)
return mid
因为有时候会有
这样的一种数列
1 2 2 2 2 3
这个是非严格单调序列,如果叫你寻找2第一次出现的位置显然会出现错误
所以上面那个写法是不行的;
2.
为啥不写left=mid+1和right=mid-1
因为有些情况下会不能用
比如在数列
a[]={0 1 3 4,5}
找大于2的第一个位置
当你right=4,left=0;
则mid=2; A[mid]>2 所以 若r=mid-1=1你就错过去了,除非你用if判断一下,但那样就太冗长难看了;
3.
为啥不能写
while(left!=right)
因为上面的原因我们没用 left=mid+1和right=mid-1
而是left=mid,right=mid
若a[]={0,1,3,4};
则找2的位置时会死循环
因为 当left=1,right=2时
mid=(left+right)/2=1;
而a[mid]<2;
所以left=mid=1;
结果left还是等于1,right还是等于2
永远死循环
现在来解释一下上面代码的原理:
我们让left代表小于等于 x的一方
right代表大于x的一方
我们每次判断mid是属于left还是属于right来使区间缩小一半;
当left与right相遇时,即left+1=right时 分界线就在left和right中间
则从left开始往前小于等于x,从right开始往后大于x
同理其实我们也可以让
if(f(mid)<x)
left=mid;
else
right=mid;
这时
left代表小于x的一方
right代表大于等于x的一方
当left与right相遇时;
则从left开始往前小于x,从right开始往后大于等于x
即如图所示
最后我们来教点小技巧
如果我把前一种记做find_upper_bound()
如果我把后一种记做find_lower_bound()
显然图观察可得 两种的二分结果分别是
x的下标上界和下标下界(包含x)
他们相减就是x的个数
显然若find_upper_bound(x)==find_lower_bound(x);那么就不存在x
但在使用时不用写两种二分,其实由观察图可得
find_upper_bound(x-1)=find_lower_bound(x);
或者find_upper_bound(x)=find_lower_bound(x+1);
所以其实写一种就行了;
这里给出对照表(前提是整数递增序列才成立,如果是浮点数或则递减序列时都不成立)
查询目标 find_upper_bound的用法 find_lower_bound的用法
大于x的第一个位置 find_upper_bound(x) find_lower_bound(x+1)
x出现的第一个位置 find_upper_bound(x-1) find_lower_bound(x)
小于x的第一个位置 find_upper_bound(x-1)-1 find_lower_bound(x)-1
x出现的最后位置 find_upper_bound(x)-1 find_lower_bound(x+1)-1
习题
1.http://210.34.193.66:8080/vj/Problem.jsp?pid=2145
2.http://210.34.193.66:8080/vj/Problem.jsp?pid=1923(这题注意要先用快速排序再二分)
这里是习题1的标程