二分查詢的詳細分析 基於迴圈不變式的分析

2021-10-01 19:40:09 字數 3620 閱讀 2303

給乙個陣列,已公升序排序,即不存在重複元素,查詢給定值target,如果不存在,返回該值在陣列中可以插入的位置。二分查詢本質是利用分治加剪枝不斷進行問題規模的縮小,到最後問題不可分解決問題。將乙個區間分為兩半(縮小規模),分別查詢,因為另乙個子問題肯定無解,不需要查詢(剪枝),所以本質是剪枝的分治。另外對二分查詢迴圈不變式分析的過程要按查詢值存在與否分類討論,且隨著迭代的深入,區間大小始終是在減半收縮。

1.1. 流程

1)將查詢區間一分為二,選取中間點,若是要查詢元素,返回

2)如果中間點小於target值,查詢右半區間,從 mid + 1 到 right

3)若中間點大於target值,查詢左半區間,從 left 到 mid - 1

4)如果區間不存在,即left > right ,結束。

1.2. 樸素實現

int binary_search(int *arr, int size, int target)  else if (arr[mid] > target)  else 

} return l;

}

1.3. 分析

1.3.1 迴圈條件解釋

因為迴圈的本質是在進行區間的收縮,因為對於乙個target來說,肯定要麼在左半區間,要麼在右半區間,或者剛好在區間中間點(會直接返回),其他情況都需要不斷查詢target在哪個半區間,而區間最小的情況是只有乙個元素,即[le

ft,l

eft]

[left, left]

[left,

left

]或者[ri

ght,

righ

t]

[right,right]

[right

,rig

ht]。

1.3.2 迴圈不變式

迴圈不變式分為兩種情況討論,如果target在陣列中,如果target不在陣列中

1. target在陣列中

target一定在區間[le

ft,r

ight

][left, right]

[left,

righ

t]中。

2. target不在陣列

迴圈不變式: 如果要插入target則一定插入在區間中的某個位置,要麼就是插入在left,或者插入在right之後。 迭代

總結迭代的情況,由於區間一直向[i,

i+1]

[i,i+1]

[i,i+1

]收縮,即區間收縮時,左邊界向i靠近,右邊界向i+1靠近,最後一定會進入迭代i、ii的情況。終止

總結:對於target不在陣列中,在迭代過程中由於區間一直向收縮,最終都會落入i或者ii類情況,即target已經在搜尋區間之外的情況,最終迭代都會結束。

1.4. 終止過程分析

本節討論迴圈如何終止的情況,分為最後只剩1個元素,2個元素,3個元素的情況,討論如下

只有乙個元素

如果只有乙個元素,那麼left=right。

有兩個元素

此時 mid = left = right - 1

有三個元素

left + 1 = mid = right - 1。

1.5. 收縮區間的邊界問題

在收縮過程中,區間只可以向左半區間或者右半區間收縮,如果區間元素個數大於2,那麼mid一定是處於left和right中間的,無論向左向右區間一定是可以正常縮小的,但是如果區間只有2個元素或者乙個元素時,由於mid的計算方式導致mid會等於left或者right,如果left和right賦值為mid,區間可能會出現無法進一步縮小的情況,所以left和right要如何賦值來使區間收縮是非常重要的細節,一不小心就可能死迴圈需要注意,尤其是left切記不可以賦值為mid,right賦值為mid時迴圈條件需要改為while (left < right)

2.1. 下界

如果陣列非降序排列,可能存在重複元素,給定target,若存在返回該值的最小下標,如果不存在返回可插入的位置。

2.1.1. 分析

此時需要返回第乙個位置,比較時如果相等,不可以直接返回,因為該位置可能是眾多target中間的位置,所以需要繼續查詢,但是對於等於的情況要如何處理,是查詢左半區間,還是查詢右半區間。

int lower_bound(int *arr, int n, int target)  else 

} return left;

}

中間值等於目標值時,搜尋左半區間

如果左半區間中沒有目標值,那麼計算出的所有mid均小於target,一直執行l=mid + 1, 那麼最後l=r時,l = mid + 1 = r + 1,即最小下標。如果左半區間有目標值,如果mid小於目標值,l = mid + 1, 如果mid等於目標值(只可能等於,因為上一輪mid是等於),那麼 r = mid - 1。進行不斷的區間收縮迭代,一定可以找到最下下標。

2.2. 上界

給定陣列非降序排列,給定target,若存在返回該值的最大下標,不存在,則返回可插入的位置。和下界問題類似,等於的情況查詢右半區間即可。要麼存在

int upper_bound(int *arr, int n, int target)  else 

} if (right >= 0 && arr[right] == target) else

}

2.2.3. 迴圈不變式分析

上述的上界和下界問題,迴圈不變式分析,如果不存在target,那麼情況和普通的查詢一致,如果存在target,分析核心見2.2.1節的思想,即分析找到target後要如何查詢區間,對區間做是否存在target來做分類討論。

2.3. 尋找帶有重複值的旋轉陣列最小值

leetcode 154

這個問題也是二分的思想求解問題,因為二分的核心思想是收縮區間,所以在這個問題也是一樣,但是這個問題對於中間值等於左右值時,無法確定查詢值在左半區間還是右半區間,但是可以確定一定在左右之間,所以此時只能簡單的通過同時縮減左右乙個單位來減少區間大小以減小問題規模。

int findmin(vector& nums)  else if(nums[mid] < nums[r])  else  else }}

return nums[l];

}

對於二分查詢問題,本質是分治,只不過其餘問題不用解而已。在乙個區間[le

ft,r

ight

][left,right]

[left,

righ

t]中查詢乙個值,值要麼在,要麼不在。如果在要麼在[le

ft,m

id−1

][left, mid - 1]

[left,

mid−

1],要麼在[mi

d+1,

righ

t]

[mid + 1, right]

[mid+1

,rig

ht],或者就在mid處。如果值不在,則討論target的插入位置是否在區間中。通過想像乙個區間被mid一分為二兩個半區間,固定不動,然後用target和mid比較以確定target所在半區間,以及要如何收縮,以此思考問題的解決過程

PHP二分演算法查詢示例及詳細分析

自己動手寫了乙個二分查詢的函式,是用php實現的。相信很多人在面試的時候也碰到過這樣的問題。好了,廢話太多了,開始吧!二分演算法查詢 param array array 要查詢的陣列 param int min key 陣列的最小下標 param int max key 陣列的最大下標 param ...

利用迴圈不變式寫出正確的二分查詢及其衍生演算法

利用迴圈不變式寫出正確的二分查詢及其衍生演算法 先看看定義 二分查詢的搜尋過程從陣列的中間元素開始,如果中間元素正好是要查詢的元素,則查詢成功 如果某一特定元素大於或者小於中間元素,則在陣列大於或小於中間元素的那一半中查詢,而且跟開始一樣從中間元素開始比較。如果在某一步驟陣列為空,則代表找不到。這種...

二叉樹求深度的遞迴的詳細分析

二叉樹求深度的遞迴的詳細分析 二叉樹求深度遞迴演算法原始碼 資料結構 typedef struct binode binode,bittree 遞迴函式 int gettreedeep bittree t 計算二叉樹的深度 a 圖,假設給出建立了這個二叉樹,使用如上給出的遞迴實現的經典演算法,這個遞...