二分查找的倾斜性(查找左边界和右边界的共同点和区别)

在排序数组中查找一个特点的数的位置即为二分查找。二分查找有很多版本,至少在十种以上。最省事的版本(或最初级的版本)往往是两边闭区间,并且判断条件是三个判断(if……else if …… else),这样比较少会因为边界条件出错,如下:

public static int simpleSearch(int [] array, int k) {
    if (array.length == 0)
        return -1;
    int left = 0, right = array.length - 1;
    while (left <= right) {
        int mid = (left + right) >> 1;
        if (array[mid] == k)
            return mid;
        else if (array[mid] < k)
            left = mid + 1;
        else
            right = mid - 1;
    }
    return -1;
}

然而这种做法的缺点也是显而易见的:

  1. 区分了数种情况,最差情况下一个循环中要两次比较,不同数据下耗时不太平均
  2. 若要查找的数不存在时,要返回查找停止的下标-1,这种方法无法做到

通常来说,要查找目标元素的最左一个或最右一个,要把函数改写成循环左闭右开的形式,即right设置为array.length。同时while 循环不加等号。最重要的是,循环中不再把“找到目标元素”作为单独的分支来处理(返回),而是把它包含到另外两个分支中。具体包含到哪两个分支:如下所示。

查找左边界:

public static int searchFirst(int [] array, int k) {
    if (array.length == 0)
        return -1;
    int left = 0, right = array.length;
    while (left < right) {
        int mid = (left + right) >> 1;
        if (array[mid] < k)
            left = mid + 1;
        else
            right = mid;
    }
    if (left == array.length)
        return -1;
    return array[left] == k ? left : -1;
}

查找右边界:

    public static int searchLast(int[] array, int k) {
        if (array.length == 0)
            return -1;
        int left = 0, right = array.length;
        while (left < right) {
            int mid = (left + right) >> 1;
            if (array[mid] <= k) {
                left = mid + 1;
            } else
                right = mid;

        }
        return array[right-1] == k ? --right : -1;
    }

这两者的代码非常相似,最大的共同点是:左边界改变的分支中,left一定是mid+1的,而右边界改变的分支中,right一定是等于mid的
两代码的不同点就在于对array[mid] == k 的处理。

查找左边界的情况中,当中间元素命中要查找的元素时,将右边界固定为mid,这样循环便能在 [left, mid)范围内查找。你可能会疑惑:如果目标数在数组中只有一个,这样把这个元素跳过了吗?答案是不会,在之后的迭代中,left 位置会逐步更新,最终和right位置一直保持在目标元素上。

查找右边界的情况中,当中间元素命中要查找的数时,让左边界left等于中间元素的下标加1,事实上跳过了这个元素。因此,循环可以在 [mid + 1, right)的区间中迭代查找。最终,右指针会和和左指针汇合在最右一个目标元素的下标+1的位置。因而最终返回的是左指针(右指针)减一的的结果。

当然,在最终返回之前,我们还要确定查找到的位置的数(左边界时对应right或left,右边界时对应right - 1)与目标数是否相等。如果相等就返回对应的位置(左边界时返回left或right, 右边界时返回right-1或left-1),否则返回-1。

还有一点要注意的是:若给出的目标数比数组中所有数都大,必须做出条件判断:要么在函数之外截断返回-1,要么如查找左边界的代码中一样,在若判断两指针汇合处的位置是数组的长度处(超出范围),直接返回负一。而查找右边界的代码中由于取的是right - 1的位置,不需判断。

在两种查找中,共同点都是一分支中left = mid + 1,另一分支中right = mid。这其实就是二分查找的倾斜性,它是由查找区间左闭右开的特性造成的。因为“左闭”,需要手动跳过左边的元素;因为“右开”,下一步的循环在逻辑上已经自动跳过了右边的元素。

以上二分查找版本是非常通用的,基本可以满足不同需求。若要求查找不到目标元素时返回查找停止的位置-1,只需修改代码最后一行即可。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值