天天看點

分查找實作(Jon Bentley:90%程式員無法正确實作)

第二十五章:二分查找實作(Jon Bentley:90%程式員無法正确實作)

作者:July

出處:結構之法算法之道

引言

    Jon Bentley:90%以上的程式員無法正确無誤的寫出二分查找代碼。也許很多人都早已聽說過這句話,但我還是想引用《程式設計珠玑》上的如下幾段文字: 

“二分查找可以解決(預排序數組的查找)問題:隻要數組中包含T(即要查找的值),那麼通過不斷縮小包含T的範圍,最終就可以找到它。一開始,範圍覆寫整個數組。将數組的中間項與T進行比較,可以排除一半元素,範圍縮小一半。就這樣反複比較,反複縮小範圍,最終就會在數組中找到T,或者确定原以為T所在的範圍實際為空。對于包含N個元素的表,整個查找過程大約要經過log(2)N次比較。 

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

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

我很驚訝:在足夠的時間内,隻有大約10%的專業程式員可以把這個小程式寫對。但寫不對這個小程式的還不止這些人:高德納在《計算機程式設計的藝術 第3卷 排序和查找》第6.2.1節的“曆史與參考文獻”部分指出,雖然早在1946年就有人将二分查找的方法公諸于世,但直到1962年才有人寫出沒有bug的二分查找程式。 ”——喬恩·本特利,《程式設計珠玑(第1版)》第35-36頁。

    你能正确無誤的寫出二分查找代碼麼?不妨一試。

二分查找代碼

     二分查找的原理想必不用多解釋了,不過有一點必須提醒讀者的是,二分查找是針對的排好序的數組。OK,紙上讀來終覺淺,覺知此事要躬行。我先來寫一份,下面是我寫的一份二分查找的實作(之前去某一家公司面試也曾被叫當場實作二分查找,不過結果可能跟你一樣,當時就未能完整無誤寫出),有任何問題或錯誤,懇請不吝指正:

  1. //二分查找V0.1實作版  
  2. //[email protected] July  
  3. //随時歡迎讀者找bug,email:[email protected]。  
  4. //首先要把握下面幾個要點:  
  5. //right=n-1 => while(left <= right) => right=middle-1;  
  6. //right=n   => while(left <  right) => right=middle;  
  7. //middle的計算不能寫在while循環外,否則無法得到更新。  
  8. int binary_search(int array[],int n,int value)  
  9. {  
  10.     int left=0;  
  11.     int right=n-1;  
  12.     //如果這裡是int right = n 的話,那麼下面有兩處地方需要修改,以保證一一對應:  
  13.     //1、下面循環的條件則是while(left < right)  
  14.     //2、循環内當array[middle]>value 的時候,right = mid  
  15.     while (left<=right)          //循環條件,适時而變  
  16.     {  
  17.         int middle=left + ((right-left)>>1);  //防止溢出,移位也更高效。同時,每次循環都需要更新。  
  18.         if (array[middle]>value)  
  19.         {  
  20.             right =middle-1;   //right指派,适時而變  
  21.         }   
  22.         else if(array[middle]<value)  
  23.         {  
  24.             left=middle+1;  
  25.         }  
  26.         else  
  27.             return middle;    
  28.         //可能會有讀者認為剛開始時就要判斷相等,但畢竟數組中不相等的情況更多  
  29.         //如果每次循環都判斷一下是否相等,将耗費時間  
  30.     }  
  31.     return -1;  
  32. }  

    簡單測試下,運作結果如下所示(當然,一次測試正确不代表程式便0 bug了,且測試深度遠遠不夠):

分查找實作(Jon Bentley:90%程式員無法正确實作)

測試

    也許你之前已經把二分查找實作過很多次了,但現在不妨再次測試一下。關閉所有網頁,視窗,打開記事本,或者編輯器,或者直接在本文評論下,不參考上面我寫的或其他任何人的程式,給自己十分鐘到N個小時不等的時間,立即編寫一個二分查找程式。獨立一次性正确寫出來後,可以留下代碼和郵箱位址,我給你傳一份本blog的博文集錦CHM檔案 && 十三個經典算法研究帶标簽+目錄的PDF文檔(你也可以去我的資源下載下傳處下載下傳:http://download.csdn.net/user/v_july_v)。

    當然,能正确寫出來不代表任何什麼,不能正确寫出來亦不代表什麼,僅僅針對Jon Bentley的言論做一個簡單的測試而已。下一章,請見第二十六章:基于給定的文檔生成反向索引的編碼與實踐。謝謝。

總結

    本文發表後,馬上就有很多朋友自己嘗試了。根據從朋友們在本文評論下留下的代碼,發現出錯率最高的在以下這麼幾個地方:

  1. 注釋裡已經說得很明白了,可還是會有不少朋友犯此類的錯誤:
    1. //首先要把握下面幾個要點:    
    2. //right=n-1 => while(left <= right) => right=middle-1;    
    3. //right=n   => while(left <  right) => right=middle;    
    4. //middle的計算不能寫在while循環外,否則無法得到更新。    
  2. 還有一個最最常犯的錯誤是@洋芋:

    middle= (left+right)>>1; 這樣的話left與right的值比較大的時候,其和可能溢出。

    各位繼續努力。     updated:各位,可以到此處0積分下載下傳本blog最新博文集錦第6期CHM檔案:http://download.csdn.net/detail/v_july_v/4020172。