二分算法

二分查找(整数二分)

1.问题1

如何在一个严格单调序列A中找出给定的数 x 。

代码

//二分区间为左闭右闭的[left, right], 传入的初值为[0, n- 1]
int binarySearch(int A[], int left, int right, int x)
{
    int mid;
    while (left <= right)
    {
        mid = left + right >> 1;
        //mid = left + (right - left) / 2;
        if (A[mid] == x) return mid;
        else if (A[mid] > x)
            right = mid - 1;
        else 
            left = mid + 1;    
    }
    return -1;
}

2.问题2

求出序列中第一个大于等于x的元素的位置L以及第一个大于x的元素的位置R,这样元素x在序列中存在的区间就是左闭右开区间[L, R)。

代码

(1)求出序列中第一个大于等于x的元素的位置。
//二分边界时左闭右闭的[left, right],初值[0, n]
int lower_bound(int A[], int left, int right, int x) 
{
    int mid;
    while (left < right)
    {
        mid = left + right >> 1;
        if (A[mid] >= x)
            right = mid;
        else 
            left = mid + 1;
    }
    return left;       //返回夹出来的位置
}
  • 注意
    1.每次循环的搜索空间是[left, right]左闭右闭,[left, right)左闭右开也可。
    3.当left == right时while循环中止,此时搜索空间是[left, left]为空,返回 left 或 right 都可。如果元素存在则返回满足条件的位置,反之返回它应该在的位置。
    4.二分的初始区间应当能覆盖到所有可能返回的结果。二分下界是很显然是0,但二分上界是 n 不是 n - 1,当查询的元素比序列中所有元素都大时返回n(即假设它存在,它应该在的位置)。故二分初始区间[left, right] = [0, n]。但二分时在n处的值没实际意义
(2)求序列中第一个大于x的元素的位置。
//二分边界时左闭右闭的[left, right],初值[0, n]
int upper_bound(int A[], int left, int right, int x)
{
    int mid;
    while (left < right)
    {
        mid = left + right >> 1;
        if (A[mid] > x)
            right = mid;
        else
            left = mid + 1;
    }
    
    return left;
}

3.模板总结

寻找有序序列中第一个满足某条件 C 的元素的位置
如果寻找最后一个满足“条件C”的元素的位置,则可以先求第一个满足“条件!C
的元素的位置。
//二分区间为左闭右闭的[left, right],初值必须能覆盖解的所有可能取值
int solve(int left, int right) 
{
    int mid;
    while(left < right)
    {
        mid = left + right / 2;
        if (条件成立)        //条件成立,第一个满足条件的元素的位置 <=mid 
        {
            right = mid;    
        }
        else    //条件不成立,第一个满足该条件的位置>mid 
        {
            left = mid + 1; 
        }
    }
    return left; 
}

4.模板拓展(区间左开右闭)

//二分区间为左闭右闭的(left, right],初值必须能覆盖解的所有可能取值
int solve(int left, int right) 
{
    int mid;
    while(left + 1 < right)
    {
        mid = left + right / 2;
        if (条件成立)        //条件成立,第一个满足条件的元素的位置 <=mid 
        {
            right = mid;    
        }
        else    //条件不成立,第一个满足该条件的位置>mid 
        {
            left = mid; 
        }
    }
    return left; 
}

浮点数二分

1.以精度位循环终止条件

while(r-l<eps)//其中eps是所需要的精度
{
    mid = (l+r)/2;
    ...
}

2.例题

1.计算$\sqrt{2}$的近似值。
const double eps = 1e-5;
double f(double x)
{
    return x * x - 2;
}

double calSqrt()
{
    double left = 1, right = 2;
    double mid;
    while (right - left > eps)
    {
        mid = (right + left) / 2;
        if (f(mid) > 0)
            right = mid;
        else
            left = mid;
    }
    return mid;    
}

上述问题实际是这个问题的特例:给定一个定义在[L, R]上的单调函数f(x),求方程f(x) = 0 的根

const double eps = 1e-5;
double f(double x)
{
    return ....;
}

double solve(double L, double R)
{
    double left = L, right = R;
    double mid;
    while (right - left > eps)
    {
        mid = (right + left) / 2;
        if (f(mid) > 0)
            right = mid;
        else
            left = mid;
    }
    return mid;    
}
2.木棒切割问题:给出N根木棒,长度一知,现在切割这些木棒至少得到K根长度相等的木棒,求长度相等的木棒最长是多长。
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 110;
int a[N];
int n, k;

int check(int l)
{
    int sum = 0;
    for (int i = 0; i < n;i ++)
        sum += a[i] / l;
    return sum;
}

int solve(int left, int right)
{
    int mid;
    while (left < right)    
    {
        mid = left + right >> 1;
        if (check(mid) < k)
            right = mid;
        else
            left = mid + 1 ;
    }
    
    return left - 1;
}

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i ++)
        scanf("%d", &a[i]);
        
    sort(a, a + n);
    
    int ans = solve(0, a[n-1] + 1);
    
    printf("%d\n", ans);
    
    return 0;
}
参考资料《算法笔记》

雨天
0 声望3 粉丝

独自灿烂


« 上一篇
区间贪心