关于二分法的探索

二分法一直被认为是最“简单”的排序方法之一,它充分地利用了数组递增性质,许多二分法的变形问题也非常经典。

1.二分

二分法最简单的实现如下:

/**
 * binary search, assure that all inputs are valid!
 */
int binary_search(int array[], int left, int right, int target)
{
    int mid = 0;   
    while(left <= right) {
        mid = (left+right)/2;
        if(array[mid] == target) return mid;
        else if(array[mid] > x) right = mid-1;
        else left = mid+1;
    }
    return -1;
}

 

值得注意的是,若二分上界超过int型数据范围的一半,那么如果查询元素在序列较靠后的位置时,语句:

mid = (left+right)/2;

中的和有可能超出int的范围而导致溢出。更合理的写法则是:

mid = left+(right-left)/2;

注意,right的初值一定是数组长度-1,否则有可能越界,其实际含义就是寻找的范围不能超过数组的范围。以上的代码可以处理有多个相同元素的递增数组(非严格递增),但不能确定找到的目标值是多个相同元素的第几个,所以引出了另一个问题:我们能不能在有重复元素的递增数组中找到第一个大于等于目标值的位置?

2.变形一:在有重复元素的递增数组中找到第一个大于等于目标值的元素的位置

分析一下问题:找到第一个大于等于目标值的元素的位置,并不一定保证该位置一定在数组范围内,所以此处right的初值为数组长度(即数组最大索引+1),表明该位置有可能取到的最大值。简单的实现如下:

/**
 * lower_bound version of binary search, assure that all inputs are valid!
 */
int lower_bound(int array[], int left, int right, int target)
{
    int mid = 0;
    while(left < right) {
        mid = (left+right)/2;
        if(array[mid] >= target) right = mid;
        else left = mid+1;
    }
    return left;
}

对比binary_search和lower_bound,可以发现有三处不同:

①left < right

为什么不加上相等的情况?因为相等情况表明已经找到该位置。

②if(array[mid] >= target) right = mid;

为什么合并相等和大于的情况,并且right = mid而不是mid-1?因为相等或大于目标值表明,mid所在的位置有可能是所找位置,也有可能在该位置的左边,因此我们应该把右边界置于此,而不应该减小,因为减小就有可能错过该位置。

③else left = mid+1;

为什么left = mid+1而不是mid?因为处于array[mid] < target的条件下,mid不可能是所寻找的位置,需要增大左边界,在新的范围内继续寻找。

3.变形二:在有重复元素的递增数组中找到第一个大于目标值的元素的位置

分析一下问题:找到第一个大于目标值的元素的位置,只在变形一的基础上做了一点改变,在代码中应该体现在寻找过程中边界上的改变:

/**
 * upper_bound version of binary search, assure that all inputs are valid!
 */
int upper_bound(int array[], int left, int right, int target)
{
    int mid = 0;
    while(left < right) {
        mid = (left+right)/2;
        if(array[mid] > target) right = mid;
        else left = mid+1;
    }
    return left;
}

对比lower_bound和upper_bound,可以发现有一处不同:

①if(array[mid] > target) right = mid;

如果理解了lower_bound的思想,那这个也很简单了,将大于等于改为大于,实际上就是等于这个条件下,mid不可能是寻找的位置,因此需要增大,所以放在else中,通过增大左边界来实现。

4.实战:木棒切割问题

问题:给出N根木棒,长度已知,现在希望通过切割它们来得到至少K段长度相等的木棒,求这些长度相等的木棒最长能有多长?

分析:首先可以确定的是,切割得到的木棒长度越长,K越小,隐含一个递增序列的条件。而每段木棒的长度最大为N根木棒的最大长度,最小为1,每个长度对应一个切割的段数,构成一个递增的序列。所以将问题转换为在该序列中,求出第一个大于等于K的位置,该位置对应的长度即为所求长度,利用变形一即可求解:

int cut_woods(int array[], int m, int k)
{
    int max = 0;
    // first, find the wood having maximum length
    for(int i = 0; i < m; ++i) {
        if(array[i] > max) max = array[i];
    }
    // second, binary search between 0 and max length
    int left = 0, right = max-1, mid = 0;
    while(left < right) {
        mid = (left+right)/2;
    int counts = 0;
    for(int i = 0; i < m; ++i) { counts += array[i]/(max-mid); }
    if(counts >= k) right = mid;
    else left = mid+1;
    }
    return max-left;           
}

需要注意的是,每段长度和段数是反比关系,所以最终的每段长度需要用最大长度max_length减去所得位置得到。

转载于:https://www.cnblogs.com/cedriccheng/p/10386405.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值