二分搜索及其問題拓展

二分搜索是適用於有序數組的高效搜索方式,本文先介紹二分搜索的遞歸和非遞歸形式,然後將其拓展到類似問題。

1. 二分搜索的基本實現

1.1 遞歸實現
def binarySearch(array, num):
    """
    採用遞歸的算法: 這種遞歸不構造新的數組,只對指針進行遞歸
    :param array:
    :param num:
    :return:
    """
    if not array:
        return False
    low, high = 0, len(array)-1
    return __binarySearch(array, num, low, high)


def __binarySearch(array, num, low, high):
    if low > high:
        return False

    mid = low + (high - low) // 2
    if array[mid] == num:
        return True

    elif array[mid] > num:
        return __binarySearch(array, num, low, mid-1)

    else:
        return __binarySearch(array, num, mid + 1, high)
1.2 非遞歸實現

def binarySearch(array, num):
    """
    採用雙指針的方式
    :param array:
    :param num:
    :return:
    """
    low, high = 0, len(array) - 1
    while low <= high:
        mid = low + (high-low)//2    # 防止溢出
        # mid = (low + high)//2
        if array[mid] == num:
            return True
        elif array[mid] < num:
            low = mid + 1
        else:
            high = mid - 1
    return False

無論是採用遞歸還是非遞歸的形式,二分法的關鍵注意點包括:
(1)確定問題縮小的方向和具體邊界。即根據mid值,將問題規模往左半區間還是右半區間縮小;邊界是開區間還是閉區間;
(2)求mid值時應注意防止數值溢出,在偶數情況下mid值可能與原low值相重複。

2. 二分搜索的問題拓展

二分法的應用場合很多,在採用二分法前需要確定問題確實可以通過這種方式進行縮減和區間確定。除了之前介紹的旋轉數組的搜索問題,這裏再簡單介紹下二分法的其他拓展,並同時給出遞歸和非遞歸的求解。

2.1 邊界範圍問題

【問題】求非下降數組的上界和下界,如[1,3,5,7,8,8,8,8,9,10]中8的下界索引爲4,上界索引爲7,若無返回-1。、
【思路】通過二分法縮小問題規模,當mid值等於目標值,如果mid位爲數組首位或者mid-1處的值小於則可確定此時mid值爲下界,否則根據mid值與目標值的大小關係進行問題二分。上界搜索的原理與之類似。

  • 遞歸方法

def lowerBoundary(array, num, lo, hi):
    """
    用遞歸的方式求解下界
    :param array: 數組
    :param num: target值
    :param lo: left指針
    :param hi: right指針
    :return:
    """
    if lo > hi:
        return -1

    mid = lo + (hi - lo)//2
    if array[mid] == num and (mid==0 or array[mid-1] < num):
        return mid

    elif array[mid] >= num:
        return lowerBoundary(array, num, lo, mid - 1)

    else:
        return lowerBoundary(array, num, mid + 1, hi)

def upperBoundary(array, num, lo, hi):
    """
    遞歸方式求解upperboundary
    :param array:
    :param num:
    :param lo: left指針
    :param hi: right指針
    :return:
    """
    if lo > hi:
        return -1

    mid = lo + (hi - lo)//2
    if array[mid] == num and (mid == len(array)-1 or array[mid+1] > num):
        return mid

    elif array[mid] <= num:
        return upperBoundary(array, num, mid+1, hi)

    else:
        return upperBoundary(array, num, lo, mid-1)
  • 非遞歸方法
def lowerBoundary(array, num):
    """
    用二分遍歷的方式進行搜索上界
    :param array:
    :param num:
    :return:
    """
    lo, hi = 0, len(array)-1
    while lo <= hi:
        mid = lo + (hi - lo)//2
        if array[mid] == num and (mid==0 or array[mid-1]<num):
            return mid
        elif array[mid] >= num:
            hi = mid - 1
        else:
            lo = mid + 1
    return -1

def upperBoundary(array, num):
    """
    二分搜索的方式進行遍歷
    :param array:
    :param num:
    :return:
    """
    lo, hi = 0, len(array) - 1
    while lo <= hi:
        mid = lo + (hi - lo)//2
        if array[mid] == num and (mid == len(array)-1 or array[mid+1]> num):
            return mid
        elif array[mid] <= num:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1
2.2 模糊邊界問題

【問題】求數組中最接近於target值的小於值或大於值。如[1,3,5,7,8,8,8,8,9,10]中8的最接近的小於值爲7, 最接近的大於值爲9,若沒有返回-1
【思路】同樣求得mid值,若mid值小於目標值且mid位處於數組末尾或者mid+1值大於等於目標值時,可確定爲最接近的小於值。否則,需要 根據mid位值與目標值的大小關係進行二分搜索。對於最接近的大於值的求解,思路與之類似。

  • 遞歸方法
def nearestSmallerValue(array, num, lo, hi):
    """
    遞歸方式
    :param array:
    :param num:
    :param lo:
    :param hi:
    :return:
    """

    if lo > hi:
        return -1

    mid = lo + (hi - lo)//2

    if array[mid] < num and (mid == len(array)-1 or array[mid+1] >= num):
        return array[mid]

    elif array[mid] < num:
        return nearestSmallerValue(array, num, mid+1, hi)

    else:
        return nearestSmallerValue(array, num, lo, mid-1)

def nearestBiggerValue(array, num, lo, hi):
    """
    遞歸方式搜索最接近目標的大值
    :param array:
    :param num:
    :param lo:
    :param hi:
    :return:
    """
    if lo > hi:
        return -1

    mid = lo + (hi - lo)//2
    if array[mid] > num and (mid==0 or array[mid-1] <= num):
        return array[mid]

    elif array[mid] > num:
        return nearestBiggerValue(array, num, lo, mid-1)

    else:
        return nearestBiggerValue(array, num, mid+1, hi)
  • 非遞歸方式
def nearestSamllerValue(array, num):
    """
    二分遍歷方式
    :param array:
    :param num:
    :return:
    """
    lo, hi = 0, len(array)-1
    while lo <= hi:
        mid = lo + (hi - lo)//2
        if array[mid] < num and (mid==len(array)-1 or array[mid+1] >= num):
            return array[mid]
        elif array[mid] < num:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1

def nearestBiggerValue(array, num):
    """
    採用二分搜索的遍歷方法進行尋值
    :param array:
    :param num:
    :return:
    """
    lo, hi = 0, len(array)-1
    while lo <= hi:
        mid = lo + (hi - lo)//2
        if array[mid] > num and (mid==0 or array[mid-1] <= num):
            return array[mid]
        elif array[mid] > num:
            hi = mid - 1
        else:
            lo = mid + 1
    return -1

由上面兩個例子不難看出,利用二分搜索進行解題的關鍵在於確定問題分解方向和邊界情況。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章