• 二分查找里的upper bound与lower bound的实现与分析


    1. 问题引入

    最近参选了学堂在线的课程数据结构(2015秋)。课程由清华大学的邓俊辉老师主讲,在完成课后作业时,遇到了这样一个题目范围查询。在这个题目中,我需要解决这样一个子问题:给定了一组已经排好序的整数集合A[0...n]和一组闭区间[L,R],求这个整数集合中落在这个区间中的点的个数。
    解决这个问题,我们很容易想到查找效率很高的二分查找,但是这又不是一般求key是否在一个数组里面的二分查找问题。对于区间左端点L,要找到数组里面大于或等于它的最小的元素的下标indexL。对于区间右端点R,要找到数组里面小于或等于它的最大的元素的下标indexR。

    2. 具体实现

    2.1 lower bound的实现

    1. 首先考虑区间左端点L,我们要找出数组里面大于或等于L的最小值。

    我们可以写出关于条件判断部分的伪代码:

    if A[mid] >= key //那么数组A[begin,mid]里面一定有我们要求的元素

    end = mid;

    else //A[mid] < key时,我们所求的元素一定在数组A[mid+1,end]里面

    begin = mid + 1;

    2. 其次我们根据判断条件,可以写出中值mid的计算公式,这里的主要问题是取上界还是取下界。

    这里考虑只还剩两个元素A[i,i+1]的情况。
    如果取上界,即:mid = (begin+end+1)/2 = (i+i+1+1)/2 = i+1,那么如果满足条件A[mid]=A[i+1]>=key,要继续进行迭代的左分支区间A[begin,mid],仍相当于上一次的区间A[begin,end],由于区间规模不会出现缩减,这样左分支就会陷入死循环。如果满足条件A[mid]=A[i+1]<key,右分支区间A[mid+1,end],相当于区间A[end+1,end]直接由于不满足循环条件就退出了。总之,不能得出正确的判断结果。


    如果取下界,即:mid = (begin+end)/2 = (i+i+1)/2 = i,那么如果满足条件A[mid]=A[i]>=key,要继续进行迭代的左分支区间A[begin,mid],相当于区间A[begin,begin],区间规模缩减到一,不会出现规模保持不变而出现死循环的现象。如果满足条件A[mid]=A[i]<key,右分支区间A[mid+1,end],相当于区间A[end,end],区间规模也缩减到一,不会出现死循环现象,可以继续向下处理。
    因此,mid的计算公式应为取下界,即:mid = (begin+end)/2。

    3. 考虑整个循环的结束条件。

    我们可以接着第二步的分析继续进行。比如说仅剩两个元素A[i]=3,A[i+1]=7,而key=5。mid=(i+i+1)/2=i, A[mid]=A[i]=3<7,继而要在区间A[i+1,i+1]内进行进一步的判断。如果不退出循环,即循环的维持条件是begin<=end,而是进一步进行处理。mid=(i+1+i+1)/2=i+1, A[mid]=A[i+1]=7>=5,继而还在区间A[i+1,i+1]上做进一步处理。由于下次的begin=i+1, end=i+1,按照之前的循环维持条件begin<=end,不能退出循环,这样就会陷入死循环。而原本是可以得出结果的,大于或等于key值的最小元素的下标应该是i+1。所以我们的循环维持条件应该改为:begin < end。这样在处理区间A[i+1,i+1]时,由于begin=i+1, end=i+1,不满足循环维持条件begin<end,退出循环。但是我们不能直接返回此时begin的下标作为结果,因为有可能会出错。我们必须加以判断,如果begin这个下标对应的元素确实大于或等于key,那么begin就是对应的下标,否则,表示我们查找的元素不存在,我们可以返回-1,表示没找到。没找到对应的情况是数组A[begin,end]里的所有元素都小于key。
    最终,我们循环维持的条件是:begin<end。

    4. lower bound的实现代码

     1 //二分查找大于或等于key值的最小的元素的下标
     2 int binSearchLowerbound(int array[], int n, int key){
     3     int begin = 0;
     4     int end = n - 1;
     5     //要能保证有正确的结果,循环的终止条件必须是:begin == end
     6     while(begin < end){
     7         //由于下面的判断条件产生了两个分支:1. [begin, mid] 2. [mid+1, end]
     8         //mid的计算方法必须采用:mid = (begin+end)/2,即向下取整的方法
     9         //才能保证两个分支都可以正常退出循环
    10         int mid = begin + (end-begin)/2;
    11         if(array[mid] < key)            //如果小于key值,则所找的元素一定位于区间[mid+1, end]中
    12             begin = mid + 1;
    13         else                            //如果大于或等于key值,则所找的元素一定位于区间[begin, mid]中
    14             end = mid;
    15     }
    16     //退出循环后有两种情况:1. 所查找的元素存在 2. 所查找的元素不存在,此时所有的元素都小于key
    17     //因此要加以判断,如果存在,返回下标。如果不存在,返回-1,表示没找到
    18     if (array[begin] >= key)
    19         return begin;
    20     else
    21         return -1;
    22 }

    2.2 upper bound的实现

    分析过程同2.1的lower bound,下面只给出具体的实现代码

     1 //二分查找小于或等于key值的最大值的下标
     2 int binSearchUpperbound(int array[], int n, int key){
     3     int begin = 0;
     4     int end = n - 1;
     5     //要能保证有正确的结果,循环的终止条件必须是:begin == end
     6     while(begin < end){
     7         //由于下面的判断条件产生了两个分支:1. [begin, mid-1] 2. [mid, end]
     8         //mid的计算方法必须采用:mid = (begin+mid+1)/2,即向上取整的方法
     9         //才能保证两个分支都可以正常退出循环
    10         int mid = begin + (end-begin+1)/2;
    11         if(array[mid] > key)                //如果大于key值,则所查找的元素一定位于区间[begin, mid-1]中
    12             end = mid - 1;
    13         else
    14             begin = mid;                    //如果小于等于key值,则所查找的元素一定位于区间[mid, end]中
    15     }
    16     //退出循环后有两种情况存在:1. 所查找的元素存在 2. 所查找的元素不存在,此时所有的元素都大于key值
    17     //因此要加以判断,如果存在,返回下标。如果不存在,返回-1,表示没找到
    18     if (array[begin] <= key)
    19         return begin;
    20     else
    21         return -1;
    22 }

    3. 测试用的主程序

    具体代码为:

     1 #include <iostream>
     2 
     3 using namespace std;
     4 
     5 const int LEN = 4;
     6 
     7 int main(int argc, const char * argv[]) {
     8     int A[LEN];
     9     printf("请输入%d个已经排序的数:
    ",LEN);
    10     for (int i = 0; i < LEN; i++)
    11         cin >> A[i];
    12     cout << "请输入要查找的左区间端点值:" << endl;
    13     int L;
    14     cin >> L;
    15     int indexL = binSearchLowerbound(A, LEN, L);
    16     printf("大于或等于%d的最小值的下标为%d
    ", L, indexL);
    17     cout << "请输入要查找的右区间端点值:" << endl;
    18     int R;
    19     cin >> R;
    20     int indexR = binSearchUpperbound(A, LEN, R);
    21     printf("小于或等于%d的最大值的下标为%d
    ", R, indexR);
    22     return 0;
    23 }
  • 相关阅读:
    nginx系列之七:限流配置
    nginx系列之六:cache服务
    nginx系列之五: 负载均衡
    nginx系列之四:web服务器
    nginx系列之三:日志配置
    nginx系列之二:配置文件解读
    nginx系列之一:nginx入门
    [面试题]25个MySQL经典面试题
    常用的 Linux iptables 规则
    java new一个对象的过程中发生了什么
  • 原文地址:https://www.cnblogs.com/Wangzhike/p/4912345.html
Copyright © 2020-2023  润新知