二分查詢演算法c語言 演算法總結 二分查詢法

2021-10-11 23:23:42 字數 4155 閱讀 6177

在leetcode的題目討論中,經常會有些大神將某一類的題目結題思路給整理了出來,感覺受益匪淺。受之啟發,萌生了自己也總結一下常見演算法的解題思路的想法,希望可以讓自己在這個總結的過程中可也融匯貫通這些思路,同時可以給後來者以啟發。

從**開始好呢?就從經典的二分查詢法開始吧,這個演算法幾乎所有學過計算機的人都知道,而且我在之前的面試中不止一次碰到過這個演算法以及這個演算法的變種。

二分查詢法的思路就是通過條件判斷每次淘汰掉一半的資料,從而可以達到乙個非常快的查詢速度。下面我們結合具體的題目來看一下。

題目描述:在乙個遞增的有序不重複的數列中查詢乙個目標數字,如果找到就返回這個數字的index, 否則返回這個數字應該插入到數列中的index。

思路:這是一道最基礎的二分查詢題,每次拿出序列中間的數並和目標數字進行比較,會有三種情況:

這個數恰好等於目標數字,找到了,返回index。

這個數比目標**明目標在右半邊的序列中,淘汰掉左半邊。

這個數比目標大說明目標在左半邊的序列中,淘汰掉右半邊。

當出現2、3這兩種情況時,我們就在對應的半邊序列中遞迴地重複上述判斷,直到這半邊的序列為空位止,這時就說明序列中並沒有對應的數字,直接返回當前的index。

**:有遞迴和非遞迴兩種實現,這裡使用非遞迴的方式。**由c++來實現。

int searchinsert(vector& nums, int target) else if(nums[mid] > target)else

}return l;

}

**分析:由於每次判斷都能去掉一半的數字所以時間複雜度是o(logn)。還有幾個細節需要注意下:

為什麼 while 迴圈的條件是 l <= r 而不是 l < r 呢?對於這種邊界的判斷我們只要考慮一下極端情況下回如何就好,如在只有乙個數字 0 的數列中查詢 0,這種情況下 l 和 r 都是 0, 如果使用 l < r 作為判斷條件很明顯是不會進入 while 迴圈進行判斷的。

為什麼不用int mid = (l + r) / 2的方式求 mid 的值呢? 當數列的長度很大的時候,l + r有可能超出 int 的最大值導致溢位,所以**中的方式較為健壯。

為什麼不相等後 r 和 l 的值不是賦值成 mid,而是要 -1 和 +1 呢?如果不相等,說明無輪如何nums[mid]都不應該被包含進下一輪的判斷,通過 -1 和 +1 來將其砍掉。

這是一道二分查詢法的變種。需要在乙個含有重複數字的遞增數列中找到目標數字的起始和結束的 index,沒有的話返回。

思路:很多人看到這個題目後的最初想法估計都是直接從前面和後面按順序查詢,但是直接查詢的複雜度是 o(n),並不是乙個好的方案。我們還是要從二分查詢法入手,但是換一種思路,使用二分法實現乙個方法找到乙個數在數列中最左側的 index, 沒有的話就返回其插入位置,這樣起始位置可以直接用這個方法,結束的位置可以通過查詢目標數 +1 的方式來實現。

所以問題就成了如何找到重複數字最左側的 index, 同上面一樣,我們還是取中間位置的數同目標數字進行對比,此時卻只有兩種狀態了:

中間數比目標數字小,說明目標數字在右半序列中,淘汰掉當前位置和左半邊。

中間數大於或者等於目標數字,這裡再分三種情況:

中間數大於目標數字,結果在左半邊序列中,淘汰掉當前位置和右半邊。

中間數等於目標數字,但是不是最左側的,結果在左半邊序列中,淘汰掉當前位置和右半邊。

中間數等於目標數字,而且就是最左側的,此時如果也淘汰掉當前位置和右半邊的話,左側的 l 座標在退出迴圈的時候就會指向當前的這個位置,恰好是返回值。

**:

vectorsearchrange(vector& nums, int target) ;

int right = findleftindex(nums, target + 1) - 1;

return ;

}//返回target最左側的index, 如果沒有則返回target應該插入的index。

int findleftindex(vector& nums, int target)else

}return l;

}

**分析:當我們查詢到 target 的起始位置後,需要判斷序列中是否含有 target, 沒有的話就可以直接返回了,否則可以繼續找結束位置。在這裡我們查詢 target + 1 的最左側的index, 這樣,無論數列中是否含有 target + 1, 這個index - 1 都是 target 在數列中的結束位置。

有乙個二維陣列,每一行中的數字都是遞增的,而且比上一行的數字都要大,查詢乙個數是否在這個陣列中。

思路:這個二維陣列其實就是乙個遞增的一維陣列將其給拆成多個子數列,所以我們可以直接使用二分法進行查詢,只要處理好序列的座標即可。

**:

bool searchmatrix(vector>& matrix, int target) 

return false;

}

**分析:這個 m 行 n 列的二維陣列總共有 m * n 個數字,我們直接將其看成乙個一維陣列來計算 mid,然後通過mid / n得到行座標, 通過mid % n得到列座標,這樣就可以確定這個中間的數。

有個 m 行 n 列的二維陣列,每一行從左到右遞增,每一列是從上到下遞增,在這個二維陣列裡查詢是否存在乙個數。

思路:這道題不使用二分法的話思路會比較明確些。我們可以從二維陣列的右上角做為起點,取這個位置的數同目標數字進行對比,同樣會有三種情況:

相等。找到了這個數,直接返回true。

比目標數字大,說明結果應該在當前位置的左側,所以將列座標 -1。

比目標數字小,說明結果應該在當前位置的下方,將行座標 +1。

重複整個過程直到找到或者座標越界為止。

**:

bool searchmatrix(vector>& matrix, int target) 

return false;

}

**分析:查詢的過程其實就是一條折線,時間複雜度為o(m + n),

二分法思路:

這個二維陣列的特點就是左上角數字最小,右下角數字最大,整體從左上角向右下角遞增。如果我們使用二分查詢法確定其中心點,就可以將這個二維陣列分解成4個小的二維陣列,如下圖所示,我們分別用1, 2,3,4 來代表。而且這4個小的二維陣列都滿足左上角數字最小,右下角數字最大,,整體從左上角向右下角遞增的特點。看到這裡我們應該想到可以使用遞迴來解決這個問題。

確定中心點後,我們將中心位置的資料 center 和目標 target 進行比較分析,然後分別討論下面三種情況:

center == target, 直接返回 true。

center > target, center是陣列4中的最小數字,所以陣列4就可以淘汰掉。

center < target, center是陣列1 中的最小數字,所以陣列1可以淘汰掉。

遞迴地在剩下的3個小陣列中尋找target。

**:

bool searchmatrix(vector>& matrix, int target) 

bool find(vector>& matrix, int rowstart, int rowend, int colstart, int colend, int target) else

}

**分析:每次判斷可以淘汰掉 1 / 4 的資料,然後重新在剩下的3個小陣列中進行遞迴地搜尋,**中使用了乙個優化技巧,就是在淘汰陣列4的時候將陣列1和3合併成乙個陣列進行遞迴,淘汰陣列1的時候合併陣列2和4。這個演算法的時間複雜度為log((4/3), m *n),在leetcode中測試發現其效率反而不如上面那個效率高。

演算法總結 二分查詢

本文首發於我的個人部落格 尾尾部落 二分查詢法作為一種常見的查詢方法,將原本是線性時間提公升到了對數時間範圍,大大縮短了搜尋時間,但它有乙個前提,就是必須在有序資料中進行查詢。二分查詢很好寫,卻很難寫對,據統計只有10 的程式設計師可以寫出沒有bug的的二分查詢 出錯原因主要集中在判定條件和邊界值的...

二分查詢 演算法總結

二分查詢也稱折半搜尋,是一種在有序陣列中查詢某一特定的元素的搜尋演算法。class solution else if nums mid mid left right 2 在left和right都很大的時候會出現溢位的情況,從而導致陣列訪問溢位。改進方法將加法變為減法 mid left right l...

演算法總結 二分查詢

二分查詢法作為一種常見的查詢方法,將原本是線性時間提公升到了對數時間範圍,大大縮短了搜尋時間,但它有乙個前提,就是必須在有序資料中進行查詢。二分查詢很好寫,卻很難寫對,據統計只有10 的程式設計師可以寫出沒有bug的的二分查詢 出錯原因主要集中在判定條件和邊界值的選擇上,很容易就會導致越界或者死迴圈...