算法 之二分查找的各种版本

关于二分查找,想必大家都非常熟悉了,正如《编程珠矶》中提到的,这是一个简单的程序,可是要实现一个无bug的版本确并非一件易事,笔者最近看了一下二分搜索,简短得概括一下。

对于不同的需求,我们面临的二分查找就会有不同的版本,就我最近遇到的几个版本好好解释一下:

  •  给定一个数组,二分查找该数组中是否存在某元素elem,如果存在返回该元素的下标,如果不存在返回-1。

这是最常见的版本,代码也不是很难。最常见的代码如下:

在数组A[ left,  right ]中寻找元素 m


int binarySearch(int A[], int left, int right, int target)
{
    int mid;
    if(left > right) 
    {
        cout << "find range error!" << endl;
        return -1;
    }
    while(left <= right) //注意此处的数据范围的空间是[ left, right ]的闭区间,每次进入循环之后保持循环的循环不变式就可以了
    {
        mid = left + (right-left)/2; //避免溢出
        assert( ( A[left] <=  A[mid] )  && (  A[mid] <= A[right] )); //确保是有序的数组
        if ( A[mid] == target ) return mid;
        else if( A[mid] > target ) right = mid-1;
        else left = mid + 1;
    }
    cout << target << " not exist in A" << endl;
    return -1;

}


二分查找的递归实现:(虽然大部分时候不适用递归,递归的函数调用开销较大,,但是这里还是实现一个递归的版本。)

int binarySearchRecursion(int A[],int left, int right,int m)
{
    if(left <= right)
    {
        int mid = left + (right-left)/2;
        if(A[mid] == m) return mid;
        else if(A[mid] > m)
        {
            right = mid-1;
            binarySearchRecursion(A,left,right,m);
        }
        else
        {
            left = mid+1; 
            binarySearchRecursion(A,left,right,m);
        }
    }
    //此处必须加上else,否则在递归调用返回之后,无论值为多少,最终都会是-1!!
    else
    {
        return -1;
    }
}

  • 给定一个数组A,二分查找该数组中是否存在某元素elem,如果存在返回该元素第一次出现的下标,如果不存在返回-1。
    注意:首先要确定在该数组中存在这样的元素elem,并且找到它的起始位置。代码如下:

int binarySearchStartPos(int A[], int left, int right, int m)
{
    int mid, tmid ;
    if(left > right)
    {
        cout << "find range error !!" << endl;
        return -1;
    }
    while(left <= right)
    {
        mid = left + (right-left)/2;
        if(A[mid] == m)    //这里首先保证我一定能够找到这样的元素m,那么既然满足这样的条件,返回值就一定不能为-1.
        {
            tmid = mid;
            //刚开始我想的是,循环不变式是:[left right]之间一定包含要寻找的元素m。
            //这样不能保证循环的终止。每次我的left和right 并没有保证下一次循环时的元素个数减少!!!
            right = mid-1;
            while(left <= right)
            {
                mid = left + (right-left)/2;
                if(A[mid] ==  m){right = mid-1;tmid = mid;}
                else if (A[mid] < m) left = mid + 1;
                else;
            }
            return tmid;
        }
        else if(A[mid] > m) right = mid-1;
        else left = mid+1;
    }
    cout << m << "not exist in A " << endl;
    return -1;
}


注意:这里的每一步的范围的缩小与上述二分查找是不一样的,在确定数组中存在元素m之后,每一步的范围都必须保证元素m一定要在考察的子数组中。


关于二分查找的优化问题也是很重要的,二分查找本身的时间复杂度已经是很好了,可是针对于不同的应用,可以采用不同的优化方式。
比如:(right - left)/2 可以使用移位来实现,又或者避免数组的上下标越界,使用 left + ( right - left ) /2 来实现。避免越界。


你可能感兴趣的:(二分查找,search,binary,变种)