經典二分查找的代碼:
{
assert(arr!=NULL&&length>0);
int low=0,high=length,mid;
while(low<=high)
{
mid=(low+high)/2;
if(arr[mid]==key) return mid;
else
{
if(arr[mid]>key) high=mid-1;
else low=mid+1;
}
}
return -1;
}
來個變形:
問題來了:在循環有序數組中查找指定元素,也就是說在類似這樣的{12,16,18,20,41,100,1,4,6,9}數組中查找指定的元素。
分析一下,這裏所說的循環有序數組,就是把一個有序數組從某個(未知)位置處截爲兩段,把前一段放到後一段的後面(數組裏的元素還是有序的,只不過最小值不一定是數組的第一個元素,而可能是其中的任何一項,從它開始逐項遞增,到數組的最後一個元素時再回到第一個元素)。
顯然傳統的二分法已經無法直接使用了,但考慮一下,如果已經知道分界點位置,那問題就簡單多了,只要先判斷一下待查元素是在分界點的左側還是右側,然後直接對那一側的半個數組使用二分查找。
那麼重點就是判定待測元素在分界點的左側還是右側的問題了,可以發現每次取mid後,就會形成兩種情況的子序列。一種情況是類似{4,6,9},他是一個正常有序的子集合,另一種情況是類似{12,16,18,20,41,100,1}的與源問題類似結構的相對複雜的子集合。顯然第一種情況是簡單的,那麼判定待測元素在分界點的簡單一側會比較容易。
第一種情況(arr[mid]>=arr[low]):當key<=arr[mid]&&key>=arr[low]時,待測元素肯定會在mid的左側;其他情形則會在mid的右側。
第二種情況(arr[mid]<arr[low]):當key<=arr[low]&&key>=arr[mid]時,待測元素肯定會在mid的右側,其他情形則會在mid的左側。
上面兩個子條件的選擇比較重要。
最後給出代碼:
int
find ( int * arr,
int low ,
int high, int
key) { int
mid ; while (low<=high)
{
mid = (low+high)/2;
if
(arr[mid] == key ) return
mid; if (arr[mid]>=arr[low])
{
if (key<=arr[mid]&&key>=arr[low]) high = mid -1;
else
low = mid +1; }
else {
if (key<=arr[high]&&key>=arr[mid]) low = mid + 1;
else
high = mid -1; }
}
return
-1; } |