递增整数序列的二分详解

点点圈 提交于 2019-11-27 09:23:41

二分搜索是一种时间复杂为log2n的算法,可以用于单调函数求根和单调序列查询的有效算法,即使数列长度高达10^9 也只需二分31次,查询速度接近常数,同时二分思想是一种很基础很重要的思想希望同学们都能掌握;

单调数列(单调递增)通用二分模板

简洁版

 

完全版

start_index =0end_index=len-1时,功能和简洁版一样;

 

1.

 

 

为啥不能写下面这句

 

ifmid==n

 

return mid

 

因为有时候会有

 

这样的一种数列

 

1 2 2 2 2 3

 

这个是非严格单调序列,如果叫你寻找2第一次出现的位置显然会出现错误

 

所以上面那个写法是不行的;

 

2.

 

为啥不写left=mid+1right=mid-1

 

因为有些情况下会不能用

 

比如在数列

 

a[]={0 1 3 4,5}

 

找大于2的第一个位置

 

当你right=4left=0;

 

mid=2A[mid]>2 所以 若r=mid-1=1你就错过去了,除非你用if判断一下,但那样就太冗长难看了;

 

3.

 

为啥不能写

 

while(left=right)

 

因为上面的原因我们没用 left=mid+1right=mid-1

 

而是left=mid,right=mid

 

a[]={0,1,3,4};

 

则找2的位置时会死循环

 

因为 left=1right=2

 

mid=(left+right)/2=1

 

a[mid]<2;

 

所以left=mid=1

 

结果left还是等于1right还是等于2

 

永远死循环

 

现在来解释一下上面代码的原理:

我们让left代表小于等于 x的一方

       right代表大于x的一方

我们每次判断mid是属于left还是属于right来使区间缩小一半;

leftright相遇时,即left+1=right时 分界线就在leftright中间

则从left开始往前小于等于x,right开始往后大于x

同理其实我们也可以让

iff(mid)<x

left=mid;

else

right=mid;

这时

left代表小于x的一方

         right代表大于等于x的一方

leftright相遇时;

则从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的标程

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!