二分搜索算法
在计算机科学中,二分搜索(binary search),也称折半搜索(half-interval search)、对数搜索(logarithmic search),是一种在有序数组中查找某一特定元素的搜索算法。
搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
二分搜索在情况下的复杂度是对数时间,进行 O ( log n ) {\displaystyle O(\log n)} O(logn) 次比较操作( n {\displaystyle n} n 在此处是数组的元素数量, O {\displaystyle O} O 是大O记号, log {\displaystyle \log } log 是对数)。
二分搜索使用常数空间,无论对任何大小的输入数据,算法使用的空间都是一样的。除非输入数据数量很少,否则二分搜索比线性搜索更快,但数组必须事先被排序。尽管特定的、为了快速搜索而设计的数据结构更有效(比如哈希表),二分搜索应用面更广。
二分搜索有许多中变种。比如分散层叠可以提升在多个数组中对同一个数值的搜索。分散层叠有效的解决了计算几何学和其他领域的许多搜索问题。指数搜索将二分搜索拓宽到无边界的列表。二分搜索树和B树数据结构就是基于二分搜索的。
最坏时间复杂度 O ( log n ) {\displaystyle O(\log n)} O(logn)
最优时间复杂度 O ( 1 ) {\displaystyle O(1)} O(1)
平均时间复杂度 O ( log n ) {\displaystyle O(\log n)} O(logn)
最坏空间复杂度
迭代: O ( 1 ) {\displaystyle O(1)} O(1)
递归: O ( log n ) {\displaystyle O(\log n)} O(logn)(无尾调用消除)
分类
搜索算法
数据结构
数组
算法
二分搜索只对有序数组有效。二分搜索先比较数组中比特素和目标值。如果目标值与中比特素相等,则返回其在数组中的位置;如果目标值小于中比特素,则搜索继续在前半部分的数组中进行。如果目标值大于中比特素,则搜索继续在数组上部分进行。由此,算法每次排除掉至少一半的待查数组。
步骤
给予一个包含 n {\displaystyle n} n 个带值元素的数组 A {\displaystyle A} A 或是记录 A 0 , ⋯   , A n − 1 {\displaystyle A_{0},\cdots ,A_{n-1}} A0,⋯,An−1 A 0 , ⋯   , A n − 1 {\displaystyle A_{0},\cdots ,A_{n-1}} A0,⋯,An−1,使 A 0 ≤ ⋯ ≤ A n − 1 {\displaystyle A_{0}\leq \cdots \leq A_{n-1}} A0≤⋯≤An−1,以及目标值 T {\displaystyle T} T T,还有下列用来搜索 T {\displaystyle T} T 在 A {\displaystyle A} A 中位置的子程序。
令
L
{\displaystyle L}
L 为
0
{\displaystyle 0}
0 ,
R
{\displaystyle R}
R 为
n
−
1
{\displaystyle n-1}
n−1 。
如果
L
>
R
{\displaystyle L>R}
L>R ,则搜索以失败告终。
令
m
{\displaystyle m}
m (中间值元素)为
⌊
(
L
+
R
)
/
2
⌋
{\displaystyle \lfloor (L+R)/2\rfloor }
⌊(L+R)/2⌋ 。(具体实现中,为防止算术溢出,一般采用
⌊
L
+
(
R
−
L
)
/
2
⌋
{\displaystyle \lfloor L+(R-L)/2\rfloor }
⌊L+(R−L)/2⌋ 代替。)
如果
A
m
<
T
{\displaystyle A_{m}<T}
Am<T ,令
L
{\displaystyle L}
L 为
m
+
1
{\displaystyle m+1}
m+1 并回到步骤二。
如果
A
m
>
T
{\displaystyle A_{m}>T}
Am>T ,令
R
{\displaystyle R}
R 为
m
−
1
{\displaystyle m-1}
m−1 并回到步骤二。
当
A
m
=
T
{\displaystyle A_{m}=T}
Am=T ,搜索结束;回传值
m
{\displaystyle m}
m 。
这个迭代步骤会持续透过两个变量追踪搜索的边界。有些实际应用会在算法的最后放入相等比较,让比较循环更快,但平均而言会多一层迭代。
大致匹配
以上程序只适用于完全匹配,也就是查找一个目标值的位置。不过,因为有序数组的顺序性,将二分搜索算法扩展到能适用大致匹配并不是很重要。举例来说,二分搜索算法可以用来计算一个赋值的排名(或称秩,比它更小的元素的数量)、前趋(下一个最小元素)、后继(下一个最大元素)以及最近邻。搜索两个值之间的元素数目的范围查询可以借由两个排名查询(又称秩查询)来运行。
排名查询可以使用调整版的二分搜索来运行。借由在成功的搜索回传
m
{\displaystyle m}
m ,以及在失败的搜索回传
L
{\displaystyle L}
L ,就会取而代之地回传了比起目标值小的元素数目。
前趋和后继查询可以借由排名查询来运行。一旦知道目标值的排名,其前趋就会是那个位于其排名位置的元素,或者排名位置的上一个元素(因为它是小于目标值的最大元素)。其后继是(数组中的)下一个元素,或是(非数组中的)前趋的下一个元素。目标值的最近邻可能是前趋或后继,取决于何者较为接近。
范围查询也是直接了当的。一旦知道两个值的排名,不小于第一个值且小于第二个值的元素数量就会是两者排名的差。这个值可以根据范围的端点是否算在范围内,或是数组是否包含其端点的对应键来增加或减少1。
复杂度分析
时间复杂度
折半搜索每次把搜索区域减少一半,时间复杂度为 O ( log n ) {\displaystyle O\left(\log n\right)} O(logn) 。(n代表集合中元素的个数)
空间复杂度
O ( 1 ) {\displaystyle O\left(1\right)} O(1) 。虽以递归形式定义,但是尾递归,可改写为循环。
应用
除直接在一个数组中查找元素外,可用在插入排序中。
示例代码
C 版本- 递归
int binary_search(const int arr[], int start, int end, int khey) {
if (start > end)
return -1;
int mid = start + (end - start) / 2; //直接平均可能会溢位,所以用此算法
if (arr[mid] > khey)
return binary_search(arr, start, mid - 1, khey);
else if (arr[mid] < khey)
return binary_search(arr, mid + 1, end, khey);
else
return mid; //最后检测相等是因为多数搜寻狀况不是大于要不就小于
}
C 版本- while 循环
int binary_search(const int arr[], int start, int end, int key) {
int ret = -1; // 未搜索到数据返回-1下标
int mid;
while (start <= end) {
mid = start + (end - start) / 2; //直接平均可能会溢位,所以用此算法
if (arr[mid] < key)
start = mid + 1;
else if (arr[mid] > key)
end = mid - 1;
else { // 后检测相等是因为多数搜寻狀况不是大于要不就小于
ret = mid;
break;
}
}
return ret; // 单一出口
}
Java 版本 递归
public static int binarySearch(int[] arr, int start, int end, int hkey){
if (start > end)
return -1;
int mid = start + (end - start)/2; //防止溢位
if (arr[mid] > hkey)
return binarySearch(arr, start, mid - 1, hkey);
if (arr[mid] < hkey)
return binarySearch(arr, mid + 1, end, hkey);
return mid;
}
Java 版本 while 循环
public static int binarySearch(int[] arr, int start, int end, int hkey){
int result = -1;
while (start <= end){
int mid = start + (end - start)/2; //防止溢位
if (arr[mid] > hkey)
end = mid - 1;
else if (arr[mid] < hkey)
start = mid + 1;
else {
result = mid ;
break;
}
}
return result;
}
Python3 版本 递归
def binary_search(arr,start,end,hkey):
if start > end:
return -1
mid = start + (end - start) / 2
if arr[mid] > hkey:
return binary_search(arr, start, mid - 1, hkey)
if arr[mid] < hkey:
return binary_search(arr, mid + 1, end, hkey)
return mid
Python3 版本 while 循环
def binary_search(arr, start, end, hkey):
while start <= end:
mid = start + (end - start) / 2
if arr[mid] < hkey:
start = mid + 1
elif arr[mid] > hkey:
end = mid - 1
else:
return mid
return -1