二分查找算法总结

版权声明:本博客为记录本人学习过程而开,内容大多从网上学习与整理所得,若侵权请告知! https://blog.csdn.net/Fly_as_tadpole/article/details/87634143

二分查找有N多种写法,有各种各样的形式,选一种合适自己,自己能够理解记住的才是最好的。

对于最最最原始的二分查找,即给出一个Key,查找出key值的索引号。 
最经典的二分查找对返回来的索引号没有任何要求,只要其对应的值是Key就可以了!!!!

使用二分查找算法的序列必须是有序序列

核心代码:

int binarySearch(int a[],int key,int left,int right)
{
     while(left+1 < right)
    {

        int mid=(left+right)/2;
        if (a[mid]==key) {return mid;}
        else if (a[mid]<key)
         {
              left=mid;
         }
        else {
              right=mid;
             }

    }
     if ( a[left]==key) return left;
     if ( a[right]==key) return right;
     return -1;
}

很多版本不同的地方其实就是在于while循环条件的判断left(low、l)、right(high、hi)和mid的关系

比较常见的有:

int binarySearch(int a[],int key,int left,int right)
{
     while(left<=right)
    {

        int mid=(left+right)/2;
        if (a[mid]==key) {return mid;}
        else if (a[mid]<key)
         {
              left=mid+1;
         }
        else {
              right=mid-1;
             }

    }
     return -1;
}

其实!!!这里while循环里面是什么取决于left、right是怎么移动的

如果是: 
left=mid+1 
right=mid-1 
那么最后停止的条件就是应该是left>right。也就是说循环的条件应该是left<=right

如果是 
left=mid 
right=mid 
那么停止的条件就是left+1>right。因为在这种情况下,当循环到right-left=1时(两个指针差相邻),两者再计算mid时就永远保持一样的right和left了。


经典的二分查找了解了,下面就是二分查找的各种“变形”

1.如果序列存在重复的Key值,返回Key值的最小下标,不存在则返回-1。


其实,对于这个问题(包括下面的这些问题),处理点都只是在一个地方,把握好这个地方后所有问题都可以迎刃而解!!!

首先,这个问题不同于经典二分查找的地方在于,在经典二分查找中,我们一旦找到

if (a[mid]==key) {

    return mid;

}

我们就return 了,也就是说程序就结束了!!! 


但是现在的问题是key在序列中有重复的,也就是说我们需要继续找,不能停止!!!

我们再来对比经典二分查找还需要改变什么:所谓二分查找核心其实就是:

         if (a[mid]<key)
         {
              left=mid;
         }
        else {
              right=mid;
             }


这个是核心,这个不能变。 
上面提到既然找到一个key值后不能return ,

a[mid]==key 应该放在哪判断??

也就是说等号放哪。等号是跟着 a[mid]< key,还是a[mid]>key???

结论:

如果是a[mid]<=key里的时候是找最大的下标。

如果是a[mid]>=key里的时候是找最小的下标。

 
具体看代码: 
下面这个是把=号(a[mid]==key)放在了a[mid]> key里。所以实现的是找最小的下标。

int binarySearch(int a[],int key,int left,int right)
{
     while(left+1 < right)
    {

        int mid=(left+right)/2;
        if (a[mid]<key)
         {
              left=mid;
         }
        else {
              right=mid;
             }

    }
     if (a[left]==key) return left;
     if (a[right]==key) return right;
     return -1;
}


这是为什么呢?其实理由很好理解,越大的下标值越靠后,而当我们碰到a[mid]==key时说明我们找到一个key值,但是不确定是不是最后一个,所以我们需要做的是在这个key之后的值中寻找,也就是把left指针移后来。 
同理,当我们希望得到最小下标值时,就需要把right指针移到左边来。这样才能不断往小下标靠拢。 
那没碰到a[mid]==key时怎么办??按照二分查找的思路继续走。

另外,我们需要有一个认识:二分查找最后的结果往往是把key值落在left和right指针所在的区间里,下面举个例子:

比如对于数组a[8]={1,2,5,7,9,12,14,15},查找8时,用上面的移动方法(left=mid,right=mid,而不是left=mid-1,right=mid-1)。我们可以得到在结束while循环时,left一定是指在7处,right一定是指在9处。

再比如a[8]={1,2,5,7,9,12,14,15},查找2时,left和right指在哪?可以肯定的是只有两种情况:left->1 right->2 或者left->2 right->5。 
到底是哪一种,取决于a[mid]==key放在哪处。 
根据前面的,当是:

        if (a[mid]<=key)
         {
              left=mid;
         }
        else {
              right=mid;
             }

一定是:left->2 right->5 
当是:

        if (a[mid]<key)
         {
              left=mid;
         }
        else {
              right=mid;
             }

一定是 left->1 right->2。 


对于查找2时有两种情况,而8只有一种情况正是由于a[mid]==key引起的不同

当然,如果查找一个比数组最小值还小的值,比如对于 a[8]={1,2,5,7,9,12,14,15}找-1,最后left,right分别指在1,2。如果是找100,left->14 right->15。

2.如果序列存在重复的Key值,返回Key值的最大下标,不存在返回-1。


int binarySearch(int a[],int key,int left,int right)
{
     while(left+1 < right)
    {

        int mid=(left+right)/2;
        if (a[mid]<=key)
         {
              left=mid;
         }
        else {
              right=mid;
             }

       }
     if (a[right]==key) return right;
     if (a[left]==key) return left;
     return -1;
}

特别需要注意的是,在得到left和right后在单独处理。注意right和left返回的次序。

3.返回大于Key值的最小下标。


这句话刚开始读了几次也没读明白,其实就是比如有 
a[8]={1,2,5,7,9,12,14,15}。希望返回大于7的最小下标,也就说说返回9的下标4。 
其实这个问题很好解决,我们只要返回7的最大下标然后+1就可以。 
具体来说用上面2的算法。 
这里需要注意的是!!!! 我们用的是:返回7最大下标的算法去获得大于7最小的下标。

int binarySearch(int a[],int key,int left,int right)
{
     while(left+1 < right)
    {

        int mid=(left+right)/2;
        if (a[mid]<=key)
         {
              left=mid;
         }
        else {
              right=mid;
             }

       }
     if (a[left]>key) return left;//考虑到比数组里最小的还小
     if (a[right]>key) return right;
     return -1;
}


4.返回小于Key值的最大下标。


同样沿用3的思路。

5.返回大于等于Key值的最小下标。


这里单独把“等于”从4里面拿出来是因为循环结束需要单独处理的部分不太一样。

int binarySearch(int a[],int key,int left,int right)
{
     while(left+1 < right)
    {

        int mid=(left+right)/2;
        if (a[mid]<=key)
         {
              left=mid;
         }
        else {
              right=mid;
             }

       }
     if (a[left]>=key) return left;
     if (a[right]>=key) return right;
     return -1;
}


6.返回小于等于Key值的最大下标。


与5类似。

总结-1


其实不管是哪一种情况,也不管是什么样新的需求。把经典的二分查找弄明白后,其他的地方都是很好理解。特别就是注意到上面已经提到过的一个东西: 
二分查找(left=mid,right=mid,这种更新方式)完之后left和right都是指在包含key的区间处的(当然这里不考虑比数组最小的还小和比数组最大的还大的两种情况。其余情况key总是落在left和right所指的值之间)。特别需要单独考虑的就是key值在序列里存在时有两种可能的情况,这个需要根据需求进一步来确定是哪一种。

除了经典的情况,数组{1,2,5,7,9,12,14,15}: 
得到大于8的最小下标值,最终:left->7,right->9 
得到小于8的最大下标值,最终: left->7,right->9

得到大于5的最小下标值,最终:left->5,right->7 
得到小于5的最大下标值,最终: left->2,right->5

数组{1,2,5,7,9,9,9,9,9,9,9,12,14,15}: 
得到等于9的最小下标值,最终:left->7,right->9 
得到等于9的最大下标值,最终: left->9,right->12

二分查找复杂度
时间复杂度是O(logn)。 
证明很容易,每次折半,假设有n个元素n,n/2,n/4…..n/2^k。假设最坏的时候折半了k次时当只剩一个元素,则有n/2^k=1 ===>k=log(n)

空间复杂度: 
递归空间复杂度=递归的深度×辅助空间的大小 
二分查找递归时辅助空间为常数,递归深度了log(n),所以空间复杂度log(n)

非递归的情况下: 
二分查找的空间复杂度为常数O(1)
 

猜你喜欢

转载自blog.csdn.net/Fly_as_tadpole/article/details/87634143