通俗易懂的二分查找及其变体问题

简单的二分查找

很容易理解,但是二分查找的前提是序列是有序的。每次都是通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。
二分查找只能用在数据是通过顺序表结构存储的数据结构上,如数组,如果是其他的结构存储则不适合用二分查找,如链表。
二分查找的时间复杂度为O(logn).
简答二分查找的代码如下:

int binarySearch(int A[], int n, int x){
        
        //left 和right为数组的左右区间下标   
        int left = 0, right =n-1, mid;
        while(left <= right){
                mid = left + ( right - left) / 2; //当数组数量较多时为了防止left+right超过int范围。 
                if(A[mid] == x) return mid; //找到x,返回下标
                else if(A[mid] > x){
                        right = mid-1;
                } else{
                        left = mid+1;
                }
        } 
        return -1; //没有找到,返回-1。 
}

二分查找变体:

如果序列A中的元素可能重复,所以查找时会出现以下变体类型的二分查找。该思想是在学习极客时间的王铮数据结构与算法时学习的,对我来说比很多书上写得更加容易理解和记忆。

查找序列中第一个等于x的元素的位置

因为数组中有重复值,找到的x不一定是第一个出现的,所以可以在上面简单二分查找的基础上判断当A[mid]=x 时,判断是不是第一个,如果此时mid=0或者A[mid-1]!=x,则说明x肯定为第一个出现;若A[mid-1]=x,则要更新right = mid-1,因为此时第一个x肯定在[left, mid-1]之间。代码如下:

//查找第一个等于x的位置 
int findfirstX(int A[], int n, int x){
        int left = 0, right = n-1;
        int mid;
        while(left <= right){
                mid = left + ((right-left) >> 1);
                if(A[mid] > x){
                        right = mid-1;
                }else if(A[mid] < x){
                        left = mid + 1;
                }else{
                        if(mid == 0 || A[mid-1] != x) return mid;
                        else right = mid-1;
                }
        }
        return -1;
}

查找序列中最后一个等于给定值的元素

该问题和上面同样的思路,要判断mid是否为n-1,或者A[mid+1]即后一个是否为x,然后进行更新返回。

//查找最后一个等于x的元素位置
int findlastX(int A[], int n, int x){
        int left = 0, right = n-1, mid;
        while(left <= right){
                mid = left + ((right-left) >> 1);
                if(A[mid] > x){
                        right = mid-1;
                }else if(A[mid] < x){
                        left = mid + 1;
                }else{
                        if(mid == n-1 || A[mid+1] != x) return mid;
                        else left = mid+1;
                }
        }
        return -1;
}

查找序列中第一个大于等于x的元素的位置

思路和前面两种类似,如果A[mid]小于要查找的值value,那要查找的值肯定在[mid+1, high]之间,如果A[mid] 大于等于给定的值x,要先看下这个mid=0 || A[mid-1] < x,则此时肯定时第一个大于等于x的值。否则A[mid-1]>x,则要找的值肯定在[left, mid-1]之间,所以更新right = mid-1;

//查找第一个大于等于x的元素位置
int Upperfind(int A[], int n, int x){
        int left = 0, right = n-1, mid;
        while(left <= right){
                mid = left + ((right-left) >> 1);
                if(A[mid] >= x){
                        if((mid==0) || (A[mid-1] < x)) return mid;
                        else right = mid-1;
                }else{
                        left = mid+1;
                }
        } 
} 

查找序列中最后一个小于等于x的元素的位置

思路同上面一样。代码如下:

//查找最后一个小于等于x的元素位置
int boundfind(int A[], int n, int x){
        int left =0, right = n-1, mid;
        while(left <= right){
                mid = left + ((right- left) >> 1);
                if(A[mid] <= x){
                        if((mid==n-1) || (A[mid+1] > x)) return mid;
                        else left = mid+1;
                }else{
                        right = mid-1;
                }
        }
} 

查找序列中第一个大于x的元素的位置

//查找第一个大于x的元素位置
int findlargeX(int A[], int n, int x){
    int left =0 ,right = n-1, mid;
    while(left <= right){
        mid = left + ((right- left) >> 1);
        if(A[mid] > x){
            if((mid==0) || (A[mid-1] < x)) return mid;
            else right = mid - 1;
        }else{
            left = mid + 1;
        }
    }
    return -1;
} 

参考资料:
极客时间 王峥算法课程:https://time.geekbang.org/column/article/42520
算法笔记 胡凡 曾磊

posted @ 2019-12-03 21:27  huanghh  阅读(...)  评论(... 编辑 收藏