《进阶指南》——二分 学习记录

二分的基础的用法是在单调序列或单调函数中进行查找。因此当问题的答案具有单调性时,就可以通过二分把求解转化为判定(根据复杂度理论,判定的难度小于求解),这使得二分的运用范围变得很广泛。进一步地, 我们还可以扩展到通过三分法去解决单峰函数的极值以及相关问题。

二分的实现方法多种多样,但是其细节之处确实需要仔细考虑。对于整数域上的二分,需要注意终止边界、左右区间取舍时的开闭情况,避免漏掉答案或造成死循环;对于实数域上的二分,需要注意精度问题。

整数集合上的二分

本书所使用的二分的写法保证最终答案处于闭区间\([l,r]\)以内,循环以l==r结束,每次二分的中间值mid会归属于左半段与右半段二者之一。

在单调递增序列a中查找≥x的数中最小的一个(即x或x的后继):

while(l<r)
{
    int mid=l+r>>1;
    if(a[mid] >= x) r=mid;
    else l=mid+1;
}
return a[l];

在单调递增序列(a中查找≤x的数中最大的一个(即x或x的前驱):

while(l<r)
{
    int mid=l+r+1>>1;
    if(a[mid] <= x) l=mid;
    else r=mid-1;
}
return a[l];

在第一段代码中,若a[mid]≥x;则根据序列a的单调性,mid之后的数会更大,所以≥x的最小的数不可能在mid之后,可行区间应该缩小为左半段。因为mid也可能是答案,故此时应取r= mid。同理,若a[mid]<x,取l=mid + 1。

在第二段代码中,若a[mid]≤x,则根据序列a的单调性,mid之前的数会更小,所以≤x的最大的数不可能在mid之前,可行区间应该缩小为右半段。因为mid也可能是答案,故此时应取l=mid。同理,若a[mid]>x, 取r=mid- 1。

如上面两段代码所示,这种二分写法可能会有两种形式:

  1. 缩小范围时,r=mid,l= mid+1,取中间值时,mid=l+r>>1。
  2. 缩小范围时,l=mid,r= mid-1,取中间值时,mid=l+r+1>>1。

如果不对mid的取法加以区分,例如第二段代码假如也采用mid= (l + r)/2,那么当r-l等于1时,就有mid=\(\lfloor (l+r)/2 \rfloor=l\)。接下来若进入l=mid分支,可行区间未缩小,造成死循环;若进入r=mid-1分支,造成l>r,循环不能以l==r结束。因此对两个形式采用配套的mid取法是必要的。上面两段代码所示的两个形式共同组成了这种二分的实现方法。

总而言之,正确写出这种二分的流程是:

  1. 通过分析具体问题,确定左右半段哪一个是可行区间,以及mid归属哪一半段。
  2. 根据分析结果,选择“r=mid,l=mid+1, mid=(l+r)/2”和“l= mid,r=mid-1,mid=(l+r+1)/2”两个配套形式之一。
    3.二分终止条件是l==r,该值就是答案所在位置。
posted @ 2021-02-10 10:33  Dazzling!  阅读(43)  评论(0编辑  收藏  举报