数据结构与算法-二分查找

什么是二分查找

  二分查找又称作折半搜索算法,是一种在有序数组中查找某一个特定元素的搜索算法;二分查找的平局时间复杂度为O(logn)。


根据实际问题来讲解二分查找

问题1:查找有序递增数组中的特定元素

  1. 查找有序递增数组中的某一个数;

  2. 从有序递增数组的中间开始,如果中间的数值刚好是要查找的数,搜索就结束;

  3.  否则,将中间的数值跟搜索的数作对比,如果中间的数比搜索的数要大,那下一次查找的范围在左边,如果中间的数比搜索的数要小,那下一次查找的范围在右边;

  4. 进入下一个范围后,再次进行上面的2,3步骤操作,直到搜索到相等的数值或者数组为空。

列如:有序递增数组a=[1,4,4,5,6],特定元素是5。L为最左边位置,R为最右边位置,M为[L,R]区间内的中间位置。

第一次比较为:L=0,R=4,M=2,a[M]比特定值5小,区间往右边搜索,所以L=M+1,最左边的位置往中间值下一位;L=3,R=4,M=3,a[M]与特定值5相等,结束搜索。

如下图所示:

上述是最基础的二分查找问题,核心思想。

//查找有序递增数组中的特定元素
int BinarySearch(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查找区域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中间值比搜索值大,取左半边区域
        if(pArrayNum[nMid] > nValue) {
            nRight = nMid - 1;
        }
        //中间值比搜索值小,取右半边区域
        else if(pArrayNum[nMid] < nValue) {
            nLeft = nMid + 1;
        }
        //找到相等值结束搜索
        else {
            return nMid;
        }
    }
    //没有找到对应值,返回-1
    return -1;
}

问题2:查找在有序递增数组中第一个等于特定元素的位置

    判断中间值比特定值大或者小跟上面问题1的思路是一样的,只要问题是当相等的时候,当数组内有多个数值跟特定元素相等,怎么判断为第一个?那我们就在相等处多加判断条件,当中间位置为0,就是第一个时或者中间位置的前一个数值不等于特定数,那么中间位置就是第一个等于特定值的;否则,我们应该往左边区域搜索,直到满足中间位置为0或者前一个数值不等于特定值。

//查找在有序递增数组中第一个等于特定元素的位置
int BinarySearch1(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查找区域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中间值比搜索值大,取左半边区域
        if(pArrayNum[nMid] > nValue) {
            nRight = nMid - 1;
        }
        //中间值比搜索值小,取右半边区域
        else if(pArrayNum[nMid] < nValue) {
            nLeft = nMid + 1;
        }
        else {
            //判断为相等时
            //如果nMid为第一个数或者上一个数不等于nValue,就能判断出nMid就是要找的元素
            if((nMid == 0) || (pArrayNum[nMid-1] != nValue )) {
                return nMid;
            }
            //否则,再次进行左区域的搜索,查找的是第一个相等的数
            else {
                nRight = nMid - 1;
            }
        }
    }
    //没有找到对应值,返回-1
    return -1;
}

问题3:查找在有序递增数组中最后一个等于特定元素的位置

    思路基本跟问题2一样,只是相等处的判断条件稍微更改一下,因为我们要找最后一个等于特定值,所以我们更改判断条件中间位置是最后一位或者下一个值是不等于特定值;否则,取右边区域搜索,直到满足中间位置是最后一位或者下一个值不等于特定值。

//查找在有序递增数组中最后一个等于特定元素的位置
int BinarySearch2(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查找区域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中间值比搜索值大,取左半边区域
        if(pArrayNum[nMid] > nValue) {
            nRight = nMid - 1;
        }
        //中间值比搜索值小,取右半边区域
        else if(pArrayNum[nMid] < nValue) {
            nLeft = nMid + 1;
        }
        else {
            //判断为相等时
            //如果nMid为最后一个数或者下一个数不等于nValue,就能判断出nMid就是要找的元素
            if((nMid == (nCount - 1)) || (pArrayNum[nMid+1] != nValue )) {
                return nMid;
            }
            //否则,再次进行右区域的搜索,查找的是最后一个相等的数
            else {
                nLeft = nMid + 1;
            }
        }
    }
    //没有找到对应值,返回-1
    return -1;
}

问题4:查找在有序递增数组中第一个大于特定元素的位置

    根据问题1的思路,判断条件只需要两个,一个是大于特定值另外一个是小于等于特定值;当小于等于特定值时,取右边区域搜索;当大于特定值时,判断中间位是否为第一位或者上一个值是否小于等于特定值,如果是,就是第一个大于特定值,否则,取右边区域搜索。

//查找在有序递增数组中第一个大于特定元素的位置
int BinarySearch3(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查找区域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中间值比搜索值大
        if(pArrayNum[nMid] > nValue) {
            //如果中间值是第一个数或者特定值大于等于上一个值,那中间值就是第一个大于特定值
            if((nMid == 0) || (pArrayNum[nMid-1] <= nValue)) {
                return nMid;
            }
            //否则,再次进行左区域的搜索,查找的是第一个大于特定值
            else {
                nRight = nMid - 1;
            }
        }
        //中间值比搜索值小或者等于,取右半边区域
        else {
            nLeft = nMid + 1;
        }
    }
    //没有找到对应值,返回-1
    return -1;
}

问题5:查找在有序递增数组中最后一个小于特定元素的位置

    问题5的判断条件基本跟问题4是相反的,当大于等于特定值时,取左边区域搜索;当小于特定值时,判断中间位是否为最后一位或者下一个值是否大于等于特定值,如果是,就是最后一个小于特定值,否则,取左边区域搜索。

//查找在有序递增数组中最后一个小于特定元素的位置
int BinarySearch4(int *pArrayNum, int nCount, int nValue) {
    int nLeft = 0;
    int nRight = nCount - 1;
    //查找区域
    while(nLeft <= nRight) {
        int nMid = (nLeft + nRight) / 2;
        //中间值比搜索值大或者,取左半边区域
        if(pArrayNum[nMid] >= nValue) {
            nRight = nMid - 1;
        }
        //中间值比搜索值小
        else if(pArrayNum[nMid] < nValue) {
            //如果中间值是最后一个或者上一个值大于等于特定值,那中间数就是最后一个小于特定值
            if((nMid == (nCount - 1)) || (pArrayNum[nMid+1] >= nValue)) {
                return nMid;
            }
            //否则,再次进行右区域的搜索,查找的是最后一个小于特定值
            else {
                nLeft = nMid + 1;
            }
        }
    }
    //没有找到对应值,返回-1
    return -1;
}

问题2,3,4,5都是从问题1变体而来,其实只要根据查找的条件去更改每一个if条件,就可以得到相应的答案,最主要是弄懂二分查找的原理,那相应的问题就能迎刃而解。

喜欢的可以关注公众号查看更多的文章

posted @ 2020-10-26 21:00  yew0  阅读(255)  评论(0编辑  收藏  举报