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


前言

我們先來(lái)看下二分搜索維基解釋

  • 在計(jì)算機(jī)科學(xué)中蛛蒙,折半搜索(英語(yǔ):half-interval search)糙箍,也稱(chēng)二分查找算法(binary search)、二分搜索法牵祟、二分搜索深夯、二分探索,是一種在有序數(shù)組中查找某一特定元素的搜索算法诺苹。搜索過(guò)程從數(shù)組的中間元素開(kāi)始咕晋,如果中間元素正好是要查找的元素,則搜索過(guò)程結(jié)束收奔;如果某一特定元素大于或者小于中間元素掌呜,則在數(shù)組大于或小于中間元素的那一半中查找,而且跟開(kāi)始一樣從中間元素開(kāi)始比較坪哄。如果在某一步驟數(shù)組為空质蕉,則代表找不到。這種搜索算法每一次比較都使搜索范圍縮小一半翩肌。

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

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

所以如果是用鏈表存儲(chǔ)的,將無(wú)法應(yīng)用二分查找棒卷。(有的面試官會(huì)問(wèn):二分查找用什么數(shù)據(jù)結(jié)構(gòu)顾孽?數(shù)組還是鏈表祝钢?)
至于“有序排列”是遞增還是遞減比规,數(shù)組中是否存在相同元素,這些都不重要拦英。不過(guò)一般情況蜒什,我們會(huì)希望數(shù)組是遞增排列奔害,且數(shù)組中的元素互不相同关拒。

如何做二分搜索

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

二分查找可以解決(預(yù)排序數(shù)組的查找)問(wèn)題:只要數(shù)組中包含T(即要查找的值)钞瀑,那么通過(guò)不斷縮小包含T的范圍沈撞,最終就可以找到它。一開(kāi)始雕什,范圍覆蓋整個(gè)數(shù)組缠俺。將數(shù)組的中間項(xiàng)與T進(jìn)行比較,可以排除一半元素贷岸,范圍縮小一半壹士。就這樣反復(fù)比較,反復(fù)縮小范圍偿警,最終就會(huì)在數(shù)組中找到T躏救,或者確定原以為T(mén)所在的范圍實(shí)際為空。對(duì)于包含N個(gè)元素的表螟蒸,整個(gè)查找過(guò)程大約要經(jīng)過(guò)log(2)N次比較盒使。

多數(shù)程序員都覺(jué)得只要理解了上面的描述,寫(xiě)出代碼就不難了七嫌;但事實(shí)并非如此忠怖。如果你不認(rèn)同這一點(diǎn),最好的辦法就是放下書(shū)本抄瑟,自己動(dòng)手寫(xiě)一寫(xiě)凡泣。試試吧。

我在貝爾實(shí)驗(yàn)室和IBM的時(shí)候都出過(guò)這道考題皮假。那些專(zhuān)業(yè)的程序員有幾個(gè)小時(shí)的時(shí)間鞋拟,可以用他們選擇的語(yǔ)言把上面的描述寫(xiě)出來(lái);寫(xiě)出高級(jí)偽代碼也可以惹资『馗伲考試結(jié)束后,差不多所有程序員都認(rèn)為自己寫(xiě)出了正確的程序褪测。于是猴誊,我們花了半個(gè)鐘頭來(lái)看他們編寫(xiě)的代碼經(jīng)過(guò)測(cè)試用例驗(yàn)證的結(jié)果。幾次課侮措,一百多人的結(jié)果相差無(wú)幾:90%的程序員寫(xiě)的程序中有bug(我并不認(rèn)為沒(méi)有bug的代碼就正確)懈叹。

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

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

下面我們來(lái)動(dòng)手寫(xiě)一下看看這傳說(shuō)中干掉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)條件,適時(shí)而變  
    {  
        int middle = left + (right-left)/2;  //使用“(left + right) / 2”可能會(huì)造成棧溢出  
  
        if (array[middle]>value)  
        {  
            right =middle-1;   //right賦值肾砂,適時(shí)而變  
        }   
        else if(array[middle]<value)  
        {  
            left=middle+1;  
        }  
        else  
            return middle;    
        //可能會(huì)有讀者認(rèn)為剛開(kāi)始時(shí)就要判斷相等列赎,但畢竟數(shù)組中不相等的情況更多  
        //如果每次循環(huán)都判斷一下是否相等,將耗費(fèi)時(shí)間  
    }

    return -1;  
}  

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

使用遞歸的二分搜索模板

簡(jiǎn)單粗暴辫塌,直接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

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


//模板源碼
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)行簡(jiǎn)單測(cè)試臼氨,通過(guò)
//你可以改變數(shù)組的大小或者value的大小來(lái)進(jìn)行更多的測(cè)試
//如有更多問(wèn)題請(qǐng)聯(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.

沒(méi)啥可說(shuō)的掺喻,經(jīng)過(guò)上面的洗禮,做這個(gè)題應(yīng)該感覺(jué)很簡(jiǎn)單

示例代碼:

 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感耙,道理是一樣的),然后相對(duì)應(yīng)的來(lái)判斷要查找的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

利用二分法找到一個(gè)數(shù)的平方根屡拨。
董老師的例程就很不錯(cuò)只酥,我就不自己寫(xiě)了


//函數(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è)矩陣的每行每列都是排好序的呀狼,也就是說(shuō)每行每列是遞增(或遞減裂允,請(qǐng)自行分析)的。
我們簡(jiǎn)單構(gòu)造這樣一個(gè)矩陣:
1 5 10 20
2 6 11 30
7 9 12 40
8 15 31 41

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


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.

有時(shí)我們并不是要尋找目標(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末十饥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子祖乳,更是在濱河造成了極大的恐慌逗堵,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凡资,死亡現(xiàn)場(chǎng)離奇詭異砸捏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)隙赁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)垦藏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人伞访,你說(shuō)我怎么就攤上這事掂骏。” “怎么了厚掷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵弟灼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我冒黑,道長(zhǎng)田绑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任抡爹,我火速辦了婚禮掩驱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冬竟。我一直安慰自己欧穴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布泵殴。 她就那樣靜靜地躺著涮帘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪笑诅。 梳的紋絲不亂的頭發(fā)上调缨,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音吆你,去河邊找鬼同蜻。 笑死,一個(gè)胖子當(dāng)著我的面吹牛早处,可吹牛的內(nèi)容都是我干的湾蔓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼砌梆,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼默责!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起咸包,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桃序,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后烂瘫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體媒熊,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奇适,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芦鳍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚷往。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖柠衅,靈堂內(nèi)的尸體忽然破棺而出皮仁,到底是詐尸還是另有隱情,我是刑警寧澤菲宴,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布贷祈,位于F島的核電站,受9級(jí)特大地震影響喝峦,放射性物質(zhì)發(fā)生泄漏势誊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一谣蠢、第九天 我趴在偏房一處隱蔽的房頂上張望键科。 院中可真熱鬧,春花似錦漩怎、人聲如沸勋颖。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饭玲。三九已至,卻和暖如春叁执,著一層夾襖步出監(jiān)牢的瞬間茄厘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工谈宛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留次哈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓吆录,卻偏偏與公主長(zhǎng)得像窑滞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子恢筝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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

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