二分搜尋

在計算機科學中，二分查詢演算法（binary search algorithm），也稱折半搜尋演算法（half-interval search algorithm）、對數搜尋演算法（logarithmic search algorithm），是一種在有序陣列中查詢某一特定元素的搜尋演算法。搜尋過程從陣列的中間元素開始，如果中間元素正好是要查詢的元素，則搜尋過程結束；如果某一特定元素大於或者小於中間元素，則在陣列大於或小於中間元素的那一半中查詢，而且跟開始一樣從中間元素開始比較。如果在某一步驟陣列為空，則代表找不到。這種搜尋演算法每一次比較都使搜尋範圍縮小一半。

二分查詢演算法在下是對數時間複雜度的，需要進行次比較操作（在此處是陣列的元素數量，是大 O 記號，是對數）。二分查詢演算法使用常數空間，對於任何大小的輸入資料，演算法使用的空間都是一樣的。除非輸入資料數量很少，否則二分查詢演算法比線性搜尋更快，但陣列必須事先被排序。儘管一些特定的、為了快速搜尋而設計的資料結構更有效（比如雜湊表），二分查詢演算法應用面更廣。

二分查詢演算法有許多種變種。比如可以提升在多個數組中對同一個數值的搜尋的速度。分散層疊有效的解決了計算幾何學和其他領域的許多搜尋問題。將二分查詢演算法拓寬到無邊界的列表。二叉搜尋樹和 B 樹資料結構就是基於二分查詢演算法的。

演演算法

二分搜尋只對有序陣列有效。二分搜尋先比較陣列中位元素和目標值。如果目標值與中位元素相等，則返回其在陣列中的位置；如果目標值小於中位元素，則搜尋繼續在前半部分的陣列中進行。如果目標值大於中位元素，則搜尋繼續在陣列上部分進行。由此，演算法每次排除掉至少一半的待查陣列。

1.1 步驟

給予一個包含個帶值元素的陣列或是記錄，使，以及目標值，還有下列用來搜尋在中位置的子程式。

令為，為。
如果，則搜尋以失敗告終。
令（中間值元素）為。（具體實現中，為防止算術溢位，一般採用代替。）
如果，令為並回到步驟二。
當，搜尋結束；回傳值。

這個疊代步驟會持續透過兩個變數追蹤搜尋的邊界。有些實際應用會在演演算法的最後放入相等比較，讓比較迴圈更快，但平均而言會多一層疊代。

1.2 大致匹配

以上程式只適用於完全匹配，也就是尋找一個目標值的位置。不過，因為有序陣列的順序性，將二分搜尋演算法擴充套件到能適用大致匹配並不是很重要。舉例來說，二分搜尋演算法可以用來計算一個賦值的排名（或稱秩，比它更小的元素的數量）、前趨（下一個最小元素）、後繼（下一個最大元素）以及最近鄰。搜尋兩個值之間的元素數目的可以藉由兩個排名查詢（又稱秩查詢）來執行。

排名查詢可以使用調整版的二分搜尋來執行。藉由在成功的搜尋回傳，以及在失敗的搜尋回傳，就會取而代之地回傳了比起目標值小的元素數目。
前趨和後繼查詢可以藉由排名查詢來執行。一旦知道目標值的排名，其前趨就會是那個位於其排名位置的元素，或者排名位置的上一個元素（因為它是小於目標值的最大元素）。其後繼是（陣列中的）下一個元素，或是（非陣列中的）前趨的下一個元素。目標值的最近鄰可能是前趨或後繼，取決於何者較為接近。
範圍查詢也是直接了當的。一旦知道兩個值的排名，不小於第一個值且小於第二個值的元素數量就會是兩者排名的差。這個值可以根據範圍的端點是否算在範圍內，或是陣列是否包含其端點的對應鍵來增加或減少 1。

複雜度分析

 時間複雜度：折半搜尋每次把搜尋區域減少一半，時間複雜度為。（n 代表集合中元素的個數）

 空間複雜度: 。雖以遞迴形式定義，但是尾遞迴，可改寫為迴圈。

應用

除直接在一個數組中查詢元素外，可用在插入排序中。

示例程式碼

4.1 C 版本 - 遞迴

int binary_search (const int arr, int start, int end, int key) {if (start> end)

		return -1;

	int mid = start + (end - start) / 2; // 直接平均可能會溢位，所以用此演算法

	if (arrmid> key)

		return binary_search (arr, start, mid - 1, key);

	else if (arrmid

4.2 C 版本 - while 迴圈

int binary_search (const int arr, int start, int end, int key) {

int ret = -1; // 未搜尋到資料返回 - 1 下標

	int mid;

	while (start key)

			end = mid - 1;

		else { // 最後檢測相等是因為多數搜尋狀況不是大於要不就小於

			ret = mid;

break;

}

	}

	return ret; // 單一出口

}

4.3 JavaScript 版本

var arr = 1, 3, 5, 7, 9, 10, 11, 12, 14, 15, 19, 20;

const binarySearch = (arr, target) => {const search = (start, end) => {if (start> end) return -1;

const mid = start + Math.floor ((end - start) / 2);

if (arrmid> target) {return search (0, mid - 1);

} else if (arrmid

4.4 Python3 版本 while 迴圈

def binary_search (arr, left, right, key):

while left key:

right = mid - 1

return -1

4.5 Python3 版本 遞迴

def binary_search (arr, start, end, key):

	if start > end:

		return -1

	mid = start + (end - start) // 2

	if arrmid > key:

		return binary_search (arr, start, mid - 1, key)

	if arrmid

4.6 C# 版本

static int binary_search (int arr, int start, int end, int key)

{

int mid;

while (start key)

end = mid - 1;

else

return mid;

}

return -1;

}

4.7 Swift 版本

import Foundation

/// 二分搜尋完全匹配

///

///- Parameters:

/// - arr: 有序陣列

/// - start: 起始位置

/// - end: 結束點

/// - key: 特點目標值

///- Returns: 返回查詢結果

func binarySearch (arr: Int, start: Int, end: Int, key: Int) -> Int? {if start> end {return nil}

let mid = start + (end - start) / 2

if arrmid > key {return binarySearch (arr: arr, start: start, end: mid - 1, key: key)

} else if arrmid

4.8 golang 遞迴版本

func binary_search (arr int, low, high, key int) int {if low> high {return -1}

	mid := low + (high-low)/2

	if arrmid > key {return binary_search (arr, low, mid-1, key)

	} else if arrmid

4.9 golang 非遞迴版本

func binarySearch (arr int, key int) int {low, high := 0, len (arr)-1

	for low arrmid {low = mid + 1}

	}

	return -1

}

4.10 Java 遞迴

public static int binarySearch (int arr, int start, int end, int key){if (start> end)

return -1;

int mid = start + (end - start)/2; // 防止溢位

if (arrmid> key)

return binarySearch (arr, start, mid - 1, key);

if (arrmid

4.11 Java while 迴圈

public static int binarySearch (int arr, int start, int end, int key){

int result = -1;

while (start key)

end = mid - 1;

else if (arrmid

4.12 Julia 版本

Julia Sample : BinarySearch

function BinarySearch (A,Key)

	left,right = 1,length (A)

	while (leftAmid

			left = mid+1

		end

	end

	return -1

end

Main Code

A = 1,3,16,31,43,354,586 # Already Arrange

println (A) # Original Array

println (BinarySearch (A,43)) # BinarySearch Search Array

println (BinarySearch (A,354)) # BinarySearch Search Array

println (BinarySearch (A,3)) # BinarySearch Search Array

歷史

在 1946 年，約翰・莫奇利在上第一次提出二分搜尋的概念。1957 年，發表了第一個應用插值搜尋的演算法。在此時，每個發表的二分搜尋演算法只對長度為 2 的冪減一的陣列有用。直到 1960 年，德里克・亨利・萊默發表了一個對於所有長度的陣列都適用的演算法。1962 年，赫爾曼・博滕布魯赫發表了一個用 ALGOL 60 寫的二分搜尋，將判斷相等的步驟放到演算法末尾。雖然將平均迭代次數增加一，但是每次迭代中的比較次數減少了 1 次。均勻二分搜尋則是史丹佛大學的 A. K. 錢德拉在 1971 年發明的。1986 年，伯納德・查澤爾和列奧尼達斯・吉巴斯引入了分散層疊來解決計算幾何中大量存在的搜尋問題。

實現中的問題

儘管二分查詢的基本思想相對簡單，但細節可以令人難以招架... — 高德納

當喬恩・本特利將二分搜尋問題佈置給專業程式設計課的學生時，百分之 90 的學生在花費數小時後還是無法給出正確的解答，主要因為這些錯誤程式在面對邊界值的時候無法執行，或返回錯誤結果。1988 年開展的一項研究顯示，20 本教科書裡只有 5 本正確實現了二分搜尋。不僅如此，本特利自己 1986 年出版的《程式設計珠璣》一書中的二分搜尋演算法存在整數溢位的問題，二十多年來無人發現。Java 語言的庫所實現的二分搜尋演算法中同樣的溢位問題存在了九年多才被修復。