细说“二分查找”

前言:

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

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

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\n",binarySearch(arr,0,N,1));
21     printf("%d\n",binarySearch(arr,0,N,3));
22     printf("%d\n",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\n",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--------\n");
51     printf("%d\n",lowerBound(arr,0,N-1,1));
52     printf("%d\n",lowerBound(arr,0,N-1,3));
53     printf("%d\n",lowerBound(arr,0,N-1,11));
54     printf("%d\n",lowerBound(arr,0,N-1,0));
55     printf("--------upper--------\n");
56     printf("%d\n",upperBound(arr,0,N-1,1));
57     printf("%d\n",upperBound(arr,0,N-1,3));
58     printf("%d\n",upperBound(arr,0,N-1,11));
59     printf("%d\n",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\n",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存在于数组中:\n");
54     printf("方式一输出:%d\n",countX(arr,0,N-1,3));
55     printf("方式二输出:%d\n",lowerBound(arr,0,N-1,3+1)-lowerBound(arr,0,N-1,3));
56 
57     printf("需要统计个数的X不存在于数组中:\n");
58     printf("方式一输出:%d\n",countX(arr,0,N-1,2));
59     printf("方式二输出:%d\n",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()方法,但其内部具体实现的话就不是很清楚了~

 

  

posted @ 2015-08-17 15:05  Sea_Sky  阅读(1457)  评论(1编辑  收藏  举报

转载请注明出处! About me