二分法

一、算法介绍:

在中学我们曾经学过求方程近似解用过二分,其中心思想可概括为:在单调序列或者单调函数中进行查找时,通过不断将答案区间减半来最终确定答案。
上述仅为思想,实际上二分的应用是很广泛的,以下将列举其几种经典应用。

注:二分的写法有很多,也需要注意相当多的细节,我们只需要保证自己的写法正确即可。

算法复杂度:\(O(logn)\)

二、算法应用:

1.在单调递增(递减)数列中查找\(\geq x\)\(\leq x\))的数中最小(大)的一个。

这可以说是二分法最基础的应用,以下举两个典型例子即可。
(1)在单增序列\(a\)中查找\(\geq x\)的数中最小的一个(即\(x\)或者\(x\)的后继):

#include <iostream>
using namespace std;
int main()
{
    int a[] = {233333, 112, 211, 322, 421, 512, 632, 745, 832, 1349, 2330};
    int l = 1, r = 10,x=345;
    while (l < r)
    {
        int mid=(l+r)>>1;
        if(a[mid]>=x)r=mid;//答案在[l,mid]区间。
        else l=mid+1;//a[mid]<x,答案在[mid+1,r]区间。
    }
    cout<<a[l]<<endl;
    return 0;

}

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

#include <iostream>
using namespace std;
int main()
{
    int a[] = {233333, 112, 211, 322, 421, 512, 632, 745, 832, 1349, 2330};
    int l = 1, r = 10,x=345;
    while (l < r)
    {
        int mid=(l+r+1)>>1;//注意这里。
        if(a[mid]<=x)l=mid;//答案在[mid,r]区间。
        else r=mid-1;//a[mid]>x,则答案在[l,mid-1]区间。
    }
    cout<<a[l]<<endl;
    return 0;

}

我们可以注意到第二种写法中是\(mid=(l+r+1)>>1\),而非\(mid=(l+r)>>1\),这是因为当\(l+1=r\)\(mid=l\),接下来若进入\(l=mid\)分支则区间并未缩小,而进入\(r=mid-1\)分支会使得\(l>r\)

根据上面的讨论我们容易注意到第一种写法中\(mid\)无法取到\(r\),第二种写法中\(mid\)无法取到\(l\),我们可以借此处理无解的情况,即将最初的答案区间扩大为\([0,n]\)或者\([1,n+1]\),若二分最终停止在越界的下标上,则证明无解。

综上,正确二分需注意以下几点:
(1)判断左右哪一个是可行区间,注意\(mid\)的归属问题。
(2)根据需要选择是采取第一种还是第二种写法。
(3)二分终止条件为\(l==r\)

原文地址:https://www.cnblogs.com/nofind/p/15725689.html