二分法的边界选择

要在有序数组中找某个数,这个数只出现一次

这个写法有三个点需要注意

  • end 是指向实际的值的
  • begin=m+1, end=m-1
  • 循环的条件是 begin<=end, 因为begin,end 都指向数组中的元素,所以相等时依然要再判断一次
int find(int x, int *a, int begin, int end) {
    while (begin <= end) {
        int m = (begin + end) / 2;
        if (a[m] == x) {
            return m;
        } else if (a[m] < x) {
            begin = m+1;
        } else {
            end = m-1;
        }
    }
    return -1;
}

int main() {
    int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    cout << find(0, a, 0, 9) << endl;
    cout << find(5, a, 0, 9) << endl;
    cout << find(9, a, 0, 9) << endl;
    return 0;
}
找到一个数字出现的区间,开始位置或者结束位置,或者插入位置
  • 数字不存在时的返回值。这种情况下,返回的值可能是不同的,比如当值不存在时,前面的代码返回的是 -1 ,这里很可能要求返回插入位置,
  • 返回值的范围。所以如果上面那个有效返回范围是数组下标0到n-1,这种情况的返回值则是 0到 n,由于有n+1 个可插入的位置
  • 在begin/end 分别取 0 和 n 后,则 m 只能是begin+(end-begin)/2,也就是(begin+end)/2,如果我们使用 m=end-(end-begin)/2, 也就是中间偏后的数,则会在 begin=n-1,end=n 时 a[m] 越界
  • 在 m 不得不取中间偏前的数字的情况下,begin 在更新时也就一定是取m+1,否则 begin=a,end=a+1 时,则会进入死循环

其实下面的这种写法可以改写成满足上面的题目的代码,所以下面这种写法通用性更好。

int find_begin(int x, int *a, int begin, int end) {
    while (begin < end) {
        int m = (begin + end) / 2;
        if (a[m] == x) {
            end = m;
        } else if (a[m] < x) {
            begin = m + 1;
        } else {
            end = m;
        }
    }
    return begin;
}

int find_back(int x, int *a, int begin, int end) {
    while (begin < end) {
        int m = (begin + end) / 2;
        if (a[m] == x) {
            begin = m + 1;
        } else if (a[m] < x) {
            begin = m + 1;
        } else {
            end = m;
        }
    }
    return begin;
}

int main() {
    int b[] = {0, 0, 0, 2, 2, 3, 3, 3, 3, 3};
    assert(find_begin(0, b, 0, 10) == 0);
    assert(find_begin(2, b, 0, 10) == 3);
    assert(find_begin(3, b, 0, 10) == 5);
    assert(find_begin(-1, b, 0, 10) == 0);
    assert(find_begin(1, b, 0, 10) == 3);
    assert(find_begin(20, b, 0, 10) == 10);

    assert(find_back(0, b, 0, 10) == 3);
    assert(find_back(2, b, 0, 10) == 5);
    assert(find_back(3, b, 0, 10) == 10);
    assert(find_back(-1, b, 0, 10) == 0);
    assert(find_back(1, b, 0, 10) == 3);
    assert(find_back(20, b, 0, 10) == 10);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值