變形二分查找法

 二分查找常用來查找指定有序集合中元素的位置,思路和代碼都比較簡單,所以大家都很熟練。二分查找貌似很多公司在面試或筆試的時候都會多少涉及到,經常會讓你在紙上直接寫代碼,所以平常只知道原理而從來不自己寫的人,可能不會太快寫出來,或是代碼有點小漏洞,所以經常敲敲常見的數據結構和代碼還是很必要的。

經典二分查找的代碼:

int Find(int arr[], int key,int length)
{
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章