標籤:
二分尋找實現(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 頁。
你能正確無誤的寫出二分尋找代碼嗎?不妨一試。
348
二分尋找代碼
二分尋找的原理想必不用多解釋了,不過有一點必須提醒讀者的是,二分尋找是針對的
排好序的數組。OK,紙上讀來終覺淺,覺知此事要躬行。我先來寫一份,下面是我寫的一
份二分尋找的實現(之前去某一家公司面試也曾被叫當場實現二分尋找,不過結果可能跟你
一樣,當時就未能完整無誤寫出),有任何問題或錯誤,懇請不吝指正:
//二分尋找V0.1 實現版
//[email protected] July
//隨時歡迎讀者找bug,email:zhoul[email protected]。
//首先要把握下面幾個要點:
//right=n-1 => while(left <= right) => right=middle-1;
//right=n => while(left < right) => right=middle;
//middle 的計算不能寫在while 迴圈外,否則無法得到更新。
int binary_search(int array[],int n,int value)
{
int left=0;
int right=n-1;
//如果這裡是int right = n 的話,那麼下面有兩處地方需要修改,以保證一一對應:
//1、下面迴圈的條件則是while(left < right)
//2、迴圈內當array[middle]>value 的時候,right = mid
while (left<=right) //迴圈條件,適時而變
{
int middle=left + ((right-left)>>1); //防止溢出,移位也更高效。同時,每次迴圈
都需要更新。
if (array[middle]>value)
{
right =middle-1; //right 賦值,適時而變
}
else if(array[middle]<value)
{
left=middle+1;
}
else
return middle;
//可能會有讀者認為剛開始時就要判斷相等,但畢竟數組中不相等的情況更多
//如果每次迴圈都判斷一下是否相等,將耗費時間
}
return -1;
349
}
簡單測試下,運行結果如下所示(當然,一次測試正確不代表程式便0 bug 了,且測試
深度遠遠不夠):
測試
也許你之前已經把二分尋找實現過很多次了,但現在不妨再次測試一下。關閉所有網頁,
視窗,開啟記事本,或者編輯器,或者直接在本文評論下,不參考上面我寫的或其他任何人
的程式,給自己十分鐘到N 個小時不等的時間,立即編寫一個二分尋找程式。獨立一次性
正確寫出來後,可以留下代碼和郵箱地址,我給你傳一份本blog 的博文集錦CHM 檔案&&
十三個經典演算法研究帶標籤+目錄的PDF 文檔(你也可以去我的資源下載處下載:
http://download.csdn.net/user/v_july_v)。
350
當然,能正確寫出來不代表任何什麼,不能正確寫出來亦不代表什麼,僅僅針對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。
二分尋找實現(Jon Bentley:90%程式員無法正確實現)