6.二分搜索模板及其變體(上)


前言

我們先來看下二分搜索維基解釋

  • 在計算機(jī)科學(xué)中,折半搜索(英語:half-interval search),也稱二分查找算法(binary search)、二分搜索法、二分搜索、二分探索,是一種在有序數(shù)組中查找某一特定元素的搜索算法。搜索過程從數(shù)組的中間元素開始,如果中間元素正好是要查找的元素,則搜索過程結(jié)束;如果某一特定元素大于或者小于中間元素,則在數(shù)組大于或小于中間元素的那一半中查找,而且跟開始一樣從中間元素開始比較。如果在某一步驟數(shù)組為空,則代表找不到。這種搜索算法每一次比較都使搜索范圍縮小一半。

我個人比較喜歡讀作“二分查找”。
這里講點二分查找的概念準(zhǔn)備:
二分查找主要解決“在一堆數(shù)中找出指定值的位置”這類問題。
由此我們可以得出以下結(jié)論,要想應(yīng)用二分查找,這一堆數(shù)必須滿足以下特征:

  • 存儲在數(shù)組中
  • 有序排列

所以如果是用鏈表存儲的,將無法應(yīng)用二分查找。(有的面試官會問:二分查找用什么數(shù)據(jù)結(jié)構(gòu)?數(shù)組還是鏈表?)
至于“有序排列”是遞增還是遞減,數(shù)組中是否存在相同元素,這些都不重要。不過一般情況,我們會希望數(shù)組是遞增排列,且數(shù)組中的元素互不相同。

如何做二分搜索

如果您認(rèn)真看了前言中附的二分查找基本概念,基本思路應(yīng)該有了,而且我相信很多報班的小伙伴之前已經(jīng)聽過甚至自己實現(xiàn)過。
這里先講個關(guān)于“二分查找”的有趣小故事

二分查找可以解決(預(yù)排序數(shù)組的查找)問題:只要數(shù)組中包含T(即要查找的值),那么通過不斷縮小包含T的范圍,最終就可以找到它。一開始,范圍覆蓋整個數(shù)組。將數(shù)組的中間項與T進(jìn)行比較,可以排除一半元素,范圍縮小一半。就這樣反復(fù)比較,反復(fù)縮小范圍,最終就會在數(shù)組中找到T,或者確定原以為T所在的范圍實際為空。對于包含N個元素的表,整個查找過程大約要經(jīng)過log(2)N次比較。

多數(shù)程序員都覺得只要理解了上面的描述,寫出代碼就不難了;但事實并非如此。如果你不認(rèn)同這一點,最好的辦法就是放下書本,自己動手寫一寫。試試吧。

我在貝爾實驗室和IBM的時候都出過這道考題。那些專業(yè)的程序員有幾個小時的時間,可以用他們選擇的語言把上面的描述寫出來;寫出高級偽代碼也可以。考試結(jié)束后,差不多所有程序員都認(rèn)為自己寫出了正確的程序。于是,我們花了半個鐘頭來看他們編寫的代碼經(jīng)過測試用例驗證的結(jié)果。幾次課,一百多人的結(jié)果相差無幾:90%的程序員寫的程序中有bug(我并不認(rèn)為沒有bug的代碼就正確)。

我很驚訝:在足夠的時間內(nèi),只有大約10%的專業(yè)程序員可以把這個小程序?qū)憣Α5珜懖粚@個小程序的還不止這些人:高德納在《計算機(jī)程序設(shè)計的藝術(shù) 第3卷 排序和查找》第6.2.1節(jié)的“歷史與參考文獻(xiàn)”部分指出,雖然早在1946年就有人將二分查找的方法公諸于世,但直到1962年才有人寫出沒有bug的二分查找程序。

——喬恩·本特利,《編程珠璣(第1版)》第35-36頁。

下面我們來動手寫一下看看這傳說中干掉90%程序員的二分查找到底如何


//[參考鳴謝](http://blog.csdn.net/v_july_v/article/details/7093204)
 
int binarySearch(int *array, int left, int right, int value)  
{
    while (left<=right)          //循環(huán)條件,適時而變  
    {  
        int middle = left + (right-left)/2;  //使用“(left + right) / 2”可能會造成棧溢出  
  
        if (array[middle]>value)  
        {  
            right =middle-1;   //right賦值,適時而變  
        }   
        else if(array[middle]<value)  
        {  
            left=middle+1;  
        }  
        else  
            return middle;    
        //可能會有讀者認(rèn)為剛開始時就要判斷相等,但畢竟數(shù)組中不相等的情況更多  
        //如果每次循環(huán)都判斷一下是否相等,將耗費(fèi)時間  
    }

    return -1;  
}  

乍看之下也就區(qū)區(qū)十來行代碼,但是其中有很多要容易犯錯的細(xì)節(jié),童鞋們需要特別注意注釋中提到的要點以及middle值的設(shè)定。

使用遞歸的二分搜索模板

簡單粗暴,直接show code


int binarySearch(int *array, int left, int right, int value)
{
    if (left > right) return -1;
    
    int mid = left + (left + right)/2;
    if (arrary[mid] == value) {
        return mid;
    } else if (array[mid]> value) {
        return    binarySearch(array, left, mid -1, value);
    } else if (array[mid]< value) {
        return    binarySearch(array, mid+1, right, value);
    }
    
}

A generic binary search template

汗,看到這個標(biāo)題以為董老師要寫C++模板,結(jié)果……
還是我自己來吧


//模板源碼
template<typename T>
bool binarySearch(vector<T> &array,T value)
{
    int left = 0;
    int right = array.size() -1;
    while ( left <= right )
    {
        int middle = left + ( right - left ) /2;
        if ( array[middle] == value )
        {
            return true;
        }
        else if ( array[middle] > value )
        {
            right = middle - 1;
        }
        else if ( array[middle] < value )
        {
            left = middle + 1;
        }
    }
    return false;
}


//在vs下進(jìn)行簡單測試,通過
//你可以改變數(shù)組的大小或者value的大小來進(jìn)行更多的測試
//如有更多問題請聯(lián)系我email:alvinyeats@gmail.com

int _tmain(int argc, _TCHAR* argv[])
{   
    vector<int> array;
    for(int i =0; i<20;i++)
        array.push_back(i);

    int value = 21;

    bool status = binarySearch<int>(array, value);
    cout << status << endl;
    return 0;
}


Search Insert Position

leetcode原題地址
Given a sorted array and a target value, return the index
the target is found. If not, return the index where it would be if it were inserted in order.
You may assume no duplicates in the array.

沒啥可說的,經(jīng)過上面的洗禮,做這個題應(yīng)該感覺很簡單

示例代碼:

 e32`123451
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int low = 0, high = nums.size()-1;

        // Invariant: the desired index is between [low, high+1]
        while (low <= high) {
            int mid = low + (high-low)/2;

            if (nums[mid] < target)
                low = mid+1;
            else
                high = mid-1;
        }

        // (1) At this point, low > high. That is, low >= high+1
        // (2) From the invariant, we know that the index is between [low, high+1], so low <= high+1. Follwing from (1), now we know low == high+1.
        // (3) Following from (2), the index is between [low, high+1] = [low, low], which means that low is the desired index
        //     Therefore, we return low as the answer. You can also return high+1 as the result, since low == high+1
        return low;    
    }
};


Search in Rotated Sorted Array

在輪轉(zhuǎn)后的有序數(shù)組上應(yīng)用二分查找
leetcode原題地址
Suppose a sorted array is rotated at some pivot unknown to you beforehand.

(i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2).

You are given a target value to search. If found in the array return its index, otherwise return -1.

You may assume no duplicate exists in the array.

該題要注意的地方是:判斷給定值到底屬于前面的區(qū)間{4,5,6,7}還是后面的區(qū)間{0,1,2}
這種數(shù)組并不是嚴(yán)格的遞增排列,所以我們先比較M與L(也可以比較M與R,道理是一樣的),然后相對應(yīng)的來判斷要查找的key值是否位于[L,M]區(qū)間,或是[M,R]區(qū)間,從而確定下一步究竟是減小R還是增大M


int rotated_binary_search(int A[], int N, int key) {
    
    int L = 0;
    int R = N - 1;

    while (L <= R) {
    // Avoid overflow, same as M=(L+R)/2
    int M = L + ((R - L) >> 1);
    if (A[M] == key) return M;

    // the bottom half is sorted
    if (A[L] <= A[M]) {
        if (A[L] <= key && key < A[M])
            R = M - 1;
        else
            L = M + 1;
    }

    // the upper half is sorted
    else {
        if (A[M] < key && key <= A[R])
            L = M + 1;
        else
            R = M - 1;
    }
    }
    
  return -1;
}

擴(kuò)展:Search in Rotated Sorted Array II
有興趣的童鞋可以試下。

Find the Square Root

利用二分法找到一個數(shù)的平方根。
董老師的例程就很不錯,我就不自己寫了


//函數(shù)功能
//輸入:待求平方根的數(shù)n
//輸出:誤差允許范圍內(nèi)n的平方根

float squareRoot(float n)
{
    float x = n;
    float y = 1;
    float e = 0.000001;  //e decides the accuracy level

    while(x - y > e)
    {
        x = (x + y)/2;
        y = n/x;
    }

    return x;
}


矩陣搜索

題目描述:Check if an element is in a M*N matrix, each row and column of which is sorted.

有題目可知,這個矩陣的每行每列都是排好序的,也就是說每行每列是遞增(或遞減,請自行分析)的。
我們簡單構(gòu)造這樣一個矩陣:
1 5 10 20
2 6 11 30
7 9 12 40
8 15 31 41

利用這個矩陣的特性我們可以運(yùn)用二分查找的思想來簡化這個問題
解決方法:不用遞歸,從矩陣的右上角(x,y)開始search(這樣有一個好處:(x,y)的左側(cè)所有點都小于matrxi[x][y], (x,y)的下側(cè)所有點都大于matrix[x][y]),在每個點都做這樣的判斷:
示例代碼:


bool isElementInMatrix(int **matrix, int M, int N, int target) {
    int row = 0;
    int column = N - 1;

    while (row < M && column >= 0) {
        if (matrix[row][column] == target) {
            return true;
        } else if (matrix[row][column] < target) {
            row++;
        } else {
            column--;
        }
    }
    return false;
}

Range Search

Given a sorted array of intergers with duplicates.Implement a function to get the start and end position of a given value.

有時我們并不是要尋找目標(biāo)值,而是尋找到“剛剛”大于給定值的值或者“剛剛”小于給定值的值。
用數(shù)學(xué)方式描述就是:在原集合中尋找包含目標(biāo)值區(qū)間的最小子集


void searchRangeHelper(int array[], int left, int right, int target, int &begin, int &end) {
    if (left > right) {
        return;
    }

    int mid = right - (right - left) / 2;
    if (array[mid] == target) {
        if (mid < begin || begin == -1) {
            begin = mid;
        }
        if (mid > end) {
            end = mid;
        }
        searchRangeHelper(array, left, mid - 1, target, begin, end);
        searchRangeHelper(array, mid + 1, right, target, begin, end);
    }
    else if (array[mid] < target) {
        searchRangeHelper(array, mid + 1, right, target, begin, end);
    }
    else {
        searchRangeHelper(array, left, mid - 1, target, begin, end);
    }
}

vector<int> searchRange(int A[], int n, int target) {
    int begin = -1, end = -1;

    searchRangeHelper(A, 0, n - 1, target, begin, end);

    vector<int> ans;
    ans.push_back(begin);
    ans.push_back(end);
    return ans;
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗。 張土汪:刷leetcod...
    土汪閱讀 12,779評論 0 33
  • LeetCode 刷題隨手記 - 第一部分 前 256 題(非會員),僅算法題,的吐槽 https://leetc...
    蕾娜漢默閱讀 17,950評論 2 36
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,993評論 19 139
  • 二分查找 二分查找算法是一種在有序數(shù)組中查找某一特定元素的搜索算法。搜素過程從數(shù)組的中間元素開始,如果中間元素正好...
    JasonDing閱讀 1,547評論 0 3
  • 人物:老兵 職業(yè):快遞 深夜碼字不是一個好習(xí)慣,可是不在現(xiàn)在的感觸中記錄下來,不知道明天還是不是現(xiàn)在這個心境 我:...
    秦家女子閱讀 306評論 0 1