二分查找是查找算法里家喻戶曉的算法了,其時間復雜度為O(logn),可是如果真的讓你立馬拿出筆寫一個二分查找的函數出來,你確定你可以比較快的完全寫對嗎?
我們的目的是從一個已經按從小到大的順序排序好的數組arr中查找值為value的元素的位置。
大體思路我們應該都很清楚:有三個游標,一個low在頭,一個high在尾,還有一個mid指向中間,如果要檢索的數據value比中間的元素arr[mid]小,那么應該在[low,mid)區間繼續查找,即將high指向mid前面那個元素(也許你可能認為是指向mid元素的位置);如果要檢索的數據value比中間的元素arr[mid]大,那么應該在(mid,high]區間繼續查找,即將low指向mid后面那個元素(也許你可能認為是指向mid元素的位置)。一直執行這個步驟來縮小搜索區間直到找到arr[k]==value返回k 或 low>high時返回-1表示沒找到。
其中的細節有很多是需要格外注意的。下面我就通過一個我的一段代碼來引出需要注意的地方。
1 typedef int DataType; 2 int binarySearch(const DataType arr[],const DataType value,size_t len) 3 { 4 int low = 0, high = len-1, mid; 5 while(low <= high) { 6 mid = low + ((high-low)>>1); //思考為什么不寫作(high+low)/2;
7 if(value-arr[mid]<1e-6 && arr[mid]-value<1e-6)//思考為何不寫作arr[mid]==value
8 return mid; 9 if(value<arr[mid]) 10 high = mid-1; //如果寫作high = mid;可以嗎
11 else
12 low = mid+1; //如果寫作low = mid;可以嗎
13 } 14 return -1; 15 }
在看完了上面的代碼后,你有木有想到代碼中注釋部分的問題?當你第一遍寫代碼的時候真的考慮到了嗎,如果沒考慮這些會有什么過果呢?下面讓我們來一一道來:
(1)第六行如果寫作mid = (high+low)/2;,有木有發現high+low有點蹊蹺?如果你看出來了,恭喜你說明你對數據類型對應的取值范圍很了解!當DataType定義為int型時,兩個int相加,不要以為不會越界哈~另外改成移位操作同樣完成了除以2一樣的效果,但是效率卻提高了。如果用移位的話一定要記得移位運算優先級很低,所以記得加括號!!記得加括號!括號!(重要的事說三遍,哈哈)
(2)第七行說好的判等呢,為嘛寫成了區間的形式?這個嘛,就要考慮代碼可重用性,因為細心的你可能會發現,傳入的第一個參數是數組類型,什么類型的數組?這里暫時定義為int,那如果是float呢?double呢?判等還用==?所以這里考慮的是普遍情況,通過將兩個數的差值在很小范圍內來表示他們相等,int時照樣適用。
(3)第10行第12行,才開始寫的時候可能會糾結是不是要減1或者加1,當然還有第五行是寫low <= high還是low < high?舉幾個讓另一種情況出現問題的例子然后你就會明白其中的奧秘了。
好了,基本上要注意主要的問題就這些了,下面給出一個用模板函數寫好的完整代碼吧!
1 #include<vector>
2 #include<iostream>
3 using namespace std; 4
5 //二分查找模板
6 template<typename T1,typename T2>
7 int binarySearch(const T1 &arr,const T2 &value,size_t len) 8 { 9 int low = 0, high = len-1, mid; 10 while(low <= high) { 11 mid = low + ((high-low)>>1); //思考為什么不寫作(high+low)/2;
12 if(value-arr[mid]<1e-6 && arr[mid]-value<1e-6)//思考為何不寫作arr[mid]==value
13 return mid; 14 if(value<arr[mid]) 15 high = mid-1; //如果寫作high = mid;可以嗎
16 else
17 low = mid+1; //如果寫作low = mid;可以嗎
18 } 19 return -1; 20 } 21
22 int main() 23 { 24 double arr[10]; 25 int i; 26 for(i=0; i<10; i++) 27 arr[i] = i; 28 for(i=-1; i<11; i++) 29 cout<<"the index of '"<<i<<"': "<<binarySearch(arr,i,10)<<endl; 30
31 cout<<endl; 32 vector<int> arr_i(arr,arr+10); 33 for(i=-1; i<11; i++) 34 cout<<"the index of '"<<i<<"': "<<binarySearch(arr_i,i,arr_i.size())<<endl; 35 return 0; 36 }
這個模板函數可以接受不同類型的數組,當然為了兼容C++ STL中的vector容器,又對參數做了小小的改進!
————————————————————我是分割線———————————————————————
附加——如果考慮到一種特殊情況:對於數組中有相同值的元素(比如[0,1,1,1,4,5,6]),
(1)查找中希望得到該值的元素最早出現的位置(返回1),應該怎么實現?
(2)查找中希望得到該值的元素最后出現的位置(返回3),應該怎么實現?
時間倉促,如果內容上有什么疑問或者錯誤之處,請指出,謝謝!