深入分析二分查詢及其變體

2022-05-02 13:48:13 字數 4247 閱讀 5029

一般的二分查詢**如下:

int search(int a, int n, int target)

return -1;

}

上面的二分查詢非常的樸實,上述二分查詢的作用當然就是:找到陣列a中等於target的元素。但是這個查詢元素隱含了乙個條件:這個陣列的元素是不包含重複元素的。這個限制可以說是非常的大。我們來看一下,假設存在重複元素,按照上述找法,找的是誰

7 7 7 7 8 10;7
即假設我們找7,顯然第一次就找到了,這個「7」在a[2]的位置,也就是我們按照上述思路找,能找到。但並不是最開始的7或者結尾的7.

假設我們要找到最開始的7,應該如何修改**

呢?先上**:

int searchfirstpos(int a, int n, int target)

if(a[low] != target)

return -1;

else

return low;

}

這個**,為何會找到最開始的7呢?我們看看發生了什麼

還是:

7 7 7 7 8 10;7
第一次後,high ->a[2],迴圈沒結束

第二次後,high->a[1],迴圈沒結束

第三次後,high->a[0],迴圈結束

迴圈結束條件喂 low == high!

我們再看乙個例子:

5 7 7 7 7 8 10;7
第一次後,high ->a[3],迴圈沒結束

第一次後,high ->a[1],迴圈沒結束

第三次後,low->a[1],迴圈結束。

再看乙個例子

2 5 7 8 9 10 12 13 13 14;13
第一次後,low ->a[5],迴圈沒結束

第二次後,high ->a[7],迴圈沒結束

第三次後,low ->a[6],迴圈沒結束

第三次後,low ->a[7],迴圈結束

總結:也就說:找出現的第乙個值,其必然結果是low == high的時候,但是為何是第乙個,而不是最後乙個呢?

這個主要取決於下面這行**:

else // a[mid] >= target

high = mid;

也就是說即使a[mid] == target,我們也會使得high == target,換句話而言,即使a[high] == target;

我們也會讓high向第乙個出現查詢值的索引位置靠攏!!!

if(a[mid] < target)

low = mid+1;

不過仍然是借鑑了傳統二分查詢的思想,mid的值小了,就讓low=mid+1;

真正要指向第乙個,要做的就是:讓high向第乙個靠攏!!!!

上述中說到找第乙個元素,要讓high向第乙個靠攏,而這裡要找最後乙個元素,則要low向最後乙個元素靠攏。

先上**:

int searchlastpos(int a, int n, int target)

if(a[high] != target)

return -1;

else

return high;

}

這裡需要注意的是下面這行**:

int mid = low+((high-low+1)>>1);

假設仍然是:

int mid = low+((high-low)>>1);

我們看會發生什麼?

以下述序列為例;

1 2 7 7 7 8 9 13;7
第一次:mid->a[3],low-->a[3];

第二次:mid->a[5],high-->a[4];

第三次:mid->a[3],low->a[3].....而此時low < high,出現死迴圈;

可以再舉出其他例子,但是結果表明,問題總是出現在最後一步,也就是最後一步總有higg-low =1;且mid一直等於low,這使得迴圈一直為死迴圈。

究其原因,是因為:/2導致的向下取整。而high-low+1可以保證向上取整!!!

那我們為何要這麼做呢?主要原因在於:

if(a[mid] > target)

high = mid-1;

即,只要a[mid]>target,high的的值總會減小。也就是說,即使我們向上取整,最終也會使得high指向正確的位置,low也會因為向上取整的原因,最終使得low和high收斂到同乙個位置(比如low->a[3]=7,high->a[4]=7.),而low則不同,low重新整理成mid,但最後一步有可能不收斂,mid的值不再重新整理時候,low的值也不重新整理,從而導致low和high不會收斂到同乙個位置。

我們稍做分析就知道,對於**:

上述問題1、2、3無論是哪個問題,當找不到target時候,low==high等於target應該處於的位置是恆成立的。因此,這道題的**:

int searchpos(int a, int n, int target)

return low;

}

這個問題也很簡單,僅僅給出思路:

絕對值最小的數當然是0,這個問題轉化為:找陣列中0的位置,若沒找到0,那麼最終low==high指向的位置的數或者low-1(或者high-1)

指向的數就是最小的。

0 1 2 4 5 6 7可能變成2 4 5 6 7 0 1

很明顯的特徵在於:有序陣列旋轉後,存在著兩個有序部分。可能我們會想到對這兩部分分別進行二分查詢,這個思路總體上是沒有問題的。但是問題在於我們如何知道這個資料轉折點在哪?又或許我們是否有必要知道呢?

當然了,我們可以按照下面這個思路去處理問題:

第一步:尋找那個資料轉折點(比如上述序列中就是7)

第二步,判斷target所屬區間**折點前還是後)

第三步:二分查詢

當然了,這個思路是完全ok的,也可以按照這個思路去處理,事實上,我開始也是這麼做的。但是實際情況是,我們根本沒有必要這麼做,沒必要去找那個資料轉折點的位置。

因為這個陣列旋轉一次後,我們只需要關注旋轉後的陣列的中間元素,乙個很重要的特點是:中間元素兩邊的子陣列至少有乙個是有序的。因此我們可以判斷target是否在這個有序子陣列中。從而決定target的搜尋區間。

先上**:

int searchinrotatedarray(int a, int n, int target) 

else

}return -1;

}

這段**可以說是相當完美!

來分析一下:

整體而言,這段**仍然採用二分查詢法。也許我們會心有餘悸,但是仔細分析發現,非常的巧妙。

if(a[mid] >= a[low]) 

這個判斷是用來表明:前半段是否是是公升序有序子串行。如果是的話:

if(target >= a[low] && target < a[mid])

high = mid-1;

如果同時要找的數大於首個數,而小於中間元素,那麼要找的數就位於有序序列之間。自然也就執行了:

high = mid-1;

該演算法的精華在於:

else
這個else是指不滿足於上述if條件的所有可能。自然也包括了二分查詢的另一半target > a[mid]。但是其作用不僅僅是這個。那他的作用是什麼呢?

我們看,要想所查詢target位於有序陣列中,他需要滿足:

a[mid] >= a[low] && target >= a[low] && target < a[mid]

或者:

a[mid] < a[low] && target > a[mid] && target <= a[high]

不滿足上述條件的時候,發生了:

low = mid+1;

或者:

high = mid-1;

這樣的意義何在呢?沒錯就是通過改變low和high的索引,改變了mid的位置,最終也就是隨著迭代的進行,使得target總可以處於乙個有序子陣列中,並找到它。也就說,最重要的**:就是那個

else

請再深入推敲上上述**。尤其是else的作用。對這個問題進一步思考:如果這個陣列存在重複元素,那麼還能進行二分查詢嗎?顯然不能,

二分查詢及其變體(Python)

二分查詢,它的時間複雜度是 o logn 其核心思想有點類似分治思想。即每次都通過跟區間中的中間元素對比,將待查詢的區間縮小為一半,直到找到要查詢的元素,或者區間被縮小為 0。但是二分查詢的 實現比較容易寫錯。你需要著重掌握它的三個容易出錯的地方 迴圈退出條件 mid 的取值,low 和 high ...

二分查詢以及變體 JS

常規二分法 function twopart nums,key return 1 小於 小於等於的最後乙個數 function twopartsmall nums,key return hi 大於 大於等於的第乙個數 function twopartbig nums,key return lo 減而...

二分法及其變體問題

1.尋找乙個元素在陣列中的位置 二分查詢 int midfind1 int lo,int hi,int tar return 1 2.查詢第乙個等於給定值的元素 查詢第乙個等於給定值的元素 int firsteqpos int lo,int hi,int tar return 1 3.查詢最後乙個等...