• 细说“二分查找”


    前言:

      二分算法很常见,也很简单。但确实很高效!有了它,我们常常可以避免"暴力"!

    -------------------------------------------------

    1.二分漏洞

      先贴一段代码,大家看看有没有问题?

     1 int binarySearch(int *arr,int l,int r,int key){
     2     while(l<r){
     3         int m=(l+r)/2;
     4         if(arr[m]==key){
     5             return m;
     6         }else if(arr[m]>key){
     7             r = m-1;
     8         }else {
     9             l = m+1;
    10         }
    11     }
    12     return -1;
    13 }

      分析:

        当你拿上述的代码进行测试的时候,一切正常,看似风平浪静...那么,问题在哪儿呢~

    • 第2行---->当数组长度为“1”的时候,无论查找什么数直接返回“-1”;
    • 第3行---->一般人这么写没什么影响,但注意到边界值的话,想想还是可能出现"l+r"溢出的;

    ----------------------------------------

    2.改进版的二分查找

     1 #include<cstdio>
     2 #define N 5
     3 
     4 /*'l'起始下标,'r'结束下标*/
     5 int binarySearch(int *arr,int l,int r,int key){
     6     while(l<=r){
     7         int m=l+(r-l)/2;
     8         if(arr[m]==key){
     9             return m;
    10         }else if(arr[m]>key){
    11             r = m-1;
    12         }else {
    13             l = m+1;
    14         }
    15     }
    16     return -1;
    17 }
    18 int main(){
    19     int arr[N]={1,3,3,5,11};
    20     printf("%d
    ",binarySearch(arr,0,N,1));
    21     printf("%d
    ",binarySearch(arr,0,N,3));
    22     printf("%d
    ",binarySearch(arr,0,N,100));
    23     return 0;
    24 }

    测试结果:

    ----------------------------------------

    3.“二分查找“key的最小(最大)下标

     1 #include<cstdio>
     2 #define N 5
     3 
     4 int binarySearch(int *arr,int l,int r,int key){
     5     while(l<=r){
     6         int m=l+(r-l)/2;
     7         if(arr[m]==key){
     8             return m;
     9         }else if(arr[m]>key){
    10             r = m-1;
    11         }else {
    12             l = m+1;
    13         }
    14     }
    15     return -1;
    16 }
    17 //已知存在一或多个‘key’,求key的最小下标
    18 int lowerBound(int *arr,int l,int r,int key){
    19     while(l<r){
    20         int m=l+(r-l)/2;
    21         if(arr[m]==key){
    22             r=m;        //为了得到更小的下标,赋值给上边界,向下逼近
    23         }else if(arr[m]>key){
    24             r=m-1;
    25         }else{
    26             l=m+1;
    27         }
    28     }
    29     return l;
    30 }
    31 
    32 int upperBound(int *arr,int l,int r,int key){
    33     //注意:补充一个特殊位
    34     r = r+1;
    35     while(l<r){
    36         int m=l+(r-l)/2;
    37         //printf("l:%d,r:%d,m:%d
    ",l,r,m);
    38         if(arr[m]==key){
    39             l=m+1;     //返回key最大下标+1(?思考为什么无法直接返回key的最大下标)
    40         }else if(arr[m]>key){
    41             r=m-1;
    42         }else{
    43             l=m+1;
    44         }
    45     }
    46     return l;
    47 }
    48 int main(){
    49     int arr[N]={1,3,3,5,11};
    50      printf("--------lower--------
    ");
    51     printf("%d
    ",lowerBound(arr,0,N-1,1));
    52     printf("%d
    ",lowerBound(arr,0,N-1,3));
    53     printf("%d
    ",lowerBound(arr,0,N-1,11));
    54     printf("%d
    ",lowerBound(arr,0,N-1,0));
    55     printf("--------upper--------
    ");
    56     printf("%d
    ",upperBound(arr,0,N-1,1));
    57     printf("%d
    ",upperBound(arr,0,N-1,3));
    58     printf("%d
    ",upperBound(arr,0,N-1,11));
    59     printf("%d
    ",upperBound(arr,0,N-1,100));
    60     return 0;
    61 }

    上述代码的实现过程如下图:

    ---------------------------------------------------

    测试结果:

    4.不仅仅只是查找:快速统计有序数列中key的个数

    分析:

      在有序数组中,本来我们大可通过一次遍历对”key“进行计数,时间复杂度:O(n);但有了二分查找!我们下面还是考虑如何提高效率吧~

      首先,我们都知道在一个有序数列中,假如同时存在多个”key“,那么用我们上面binarySearch()函数返回的是数列中其中一个key的下标(具有随机性,跟key在数列的位置有关!)

    怎么求解呢?其实,我们上面的例子就是为这一小节的内容准备的。但是我们需要考虑2种情况,一种是要查找的key本来就不存在数列中,第二种是key存在数列中。保险一点的做法是,我们先用普通的二分查找判断一下是否存在,若存在再使用上一小节的lowerBound()和upperBound()方法。但,巧妙的是不管key在不在我们都可以使用上一小节的方法。因为我们要计算的是差值,不是具体的下标!所以,

      方法一:利用lowerBound()和upperBound()算出数列中key的最大和最小坐标i,j之后,进行相减;

      方法二:只需要lowerBound()方法。这里应用了一点小技巧,即:要查找数列中有多少个”key“,那么我利用lowerBound()函数分别得到"key"和”(key+1)“的起始坐标值(不管”key+1“实际存不存在),两者相减即是答案!

     1 #include<cstdio>
     2 #define N 5
     3 
     4 int binarySearch(int *arr,int l,int r,int key){
     5     while(l<=r){
     6         int m=l+(r-l)/2;
     7         if(arr[m]==key){
     8             return m;
     9         }else if(arr[m]>key){
    10             r = m-1;
    11         }else {
    12             l = m+1;
    13         }
    14     }
    15     return -1;
    16 }
    17 //已知存在一或多个‘key’,求key的最小下标
    18 int lowerBound(int *arr,int l,int r,int key){
    19     while(l<r){
    20         int m=l+(r-l)/2;
    21         if(arr[m]==key){
    22             r=m;        //为了得到更小的下标,赋值给上边界,向下逼近
    23         }else if(arr[m]>key){
    24             r=m-1;
    25         }else{
    26             l=m+1;
    27         }
    28     }
    29     return l;
    30 }
    31 
    32 int upperBound(int *arr,int l,int r,int key){
    33     //注意:补充一个特殊位
    34     r = r+1;
    35     while(l<r){
    36         int m=l+(r-l)/2;
    37         //printf("l:%d,r:%d,m:%d
    ",l,r,m);
    38         if(arr[m]==key){
    39             l=m+1;     //返回key最大下标+1(?思考为什么无法直接返回key的最大下标)
    40         }else if(arr[m]>key){
    41             r=m-1;
    42         }else{
    43             l=m+1;
    44         }
    45     }
    46     return l;
    47 }
    48 int countX(int *a,int l,int r,int key){
    49     return upperBound(a,l,r,key)-lowerBound(a,l,r,key);
    50 }
    51 int main(){
    52     int arr[N]={1,3,3,5,11};
    53     printf("需要统计个数的X存在于数组中:
    ");
    54     printf("方式一输出:%d
    ",countX(arr,0,N-1,3));
    55     printf("方式二输出:%d
    ",lowerBound(arr,0,N-1,3+1)-lowerBound(arr,0,N-1,3));
    56 
    57     printf("需要统计个数的X不存在于数组中:
    ");
    58     printf("方式一输出:%d
    ",countX(arr,0,N-1,2));
    59     printf("方式二输出:%d
    ",lowerBound(arr,0,N-1,2+1)-lowerBound(arr,0,N-1,2));
    60 
    61     return 0;
    62 }

    测试结果:

    -----------------------------

    注意

    • 使用”二分查找“算法的前提是数列必须有序。所以,自己给数据进行测试时请保证数据已排好序。
    • 以上实现的求"key"最小下标查找及统计"key"个数的功能,其实C++内部STL中已经有对应lower_bound()方法和unique()方法,但其内部具体实现的话就不是很清楚了~

      

  • 相关阅读:
    3、Linux 针对某一个特定用户、单一文件或者目录进行r,w,x的权限设置
    8、shell 打印实时时间日志
    35、shell 的日志不输出到终端
    34、Linux下几种文件传输命令 sz rz sftp scp
    33、umount: /storage: target is busy
    32、Device or resource busy故障处理
    30、终端不打印输出;判断命令是否执行成功;
    29、tar 命令压缩时报错 tar: Removing leading `/' from member names
    28、awk 截取字符串
    27、在脚本里面使用ssh 和 awk 时
  • 原文地址:https://www.cnblogs.com/SeaSky0606/p/4736639.html
Copyright © 2020-2023  润新知