算法——二分查找
二分查找(英语:binary search),也称折半查找(英语:half-interval search)、对数搜索(英语:logarithmic search),是用来在一个有序数组中查找某一元素的算法。
二分查找算法仅适用于有序序列,它只能用在升序序列或者降序序列中查找目标元素。
一、工作原理
以在一个升序数组中查找一个数为例。
它每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找;如果中间元素大于所查找的值同理,只需到左侧查找。
二、性质
(1)时间复杂度
二分查找的最优时间复杂度为O(1) 。
二分查找的平均时间复杂度和最坏时间复杂度均为 O(logn)。因为在二分搜索过程中,算法每次都把查询的区间减半,所以对于一个长度为n 的数组,至多会进行 O(logn) 次查找。
(2)空间复杂度
迭代版本的二分查找的空间复杂度为O(1) 。
递归(无尾调用消除)版本的二分查找的空间复杂度为O(logn) 。
三、二分查找算法原理
(1)若待查序列为空,则返回-1,并退出算法;
(2)若待查序列不为空,则将它的中间元素与目标数值进行比较,判断是否相等;
(3)若相等,则返回中间元素索引,并退出算法;此时已查找成功。
(4)若不相等,则比较中间元素与目标数值的大小;
(5)若中间元素 > 目标数值,则将当前序列的前半部分作为新的待查序列;
(6)若中间元素 < 目标数值,则将当前序列的后半部分作为新的待查序列;
(7)在新的序列上重新从第(1)步开始查找。
基本步骤
- 初始化:设定两个指针,
left
和right
,分别指向数组的起始和末尾。 - 计算中间位置:计算中间位置
mid
,mid = left + (right - left) // 2
。 - 比较:
- 如果
arr[mid]
等于目标值target
,则查找成功,返回mid
。 - 如果
arr[mid]
小于目标值target
,则目标值在右半部分,更新left = mid + 1
。 - 如果
arr[mid]
大于目标值target
,则目标值在左半部分,更新right = mid - 1
。
- 如果
- 重复:重复步骤2和步骤3,直到
left
大于right
,此时查找失败,返回 -1。
四、以在升序序列中查找目标元素为例,二分查找算法的实现思路是:
(1)初始状态下,将整个序列作为搜索区域(假设为 [B, E]);
(2)找到搜索区域内的中间元素(假设所在位置为 M),和目标元素进行比对。如果相等,则搜索成功;如果中间元素大于目标元素,表明目标元素位于中间元素的左侧,将 [B, M-1] 作为新的搜素区域;反之,若中间元素小于目标元素,表明目标元素位于中间元素的右侧,将 [M+1, E] 作为新的搜素区域;
(3)重复执行第二步,直至找到目标元素。如果搜索区域无法再缩小,且区域内不包含任何元素,表明整个序列中没有目标元素,查找失败。
五、实例讲解
在下图所示的升序序列中查找元素 31。
二分查找算法的具体实现过程为:
(1)初始状态下,搜索区域是整个序列。找到搜索区域内的中间元素。指定区域内中间元素的位置可以套用如下公式求出:
Mid = ⌊ Begin + (End - Begin) / 2 ⌋
End 表示搜索区域内最后一个元素所在位置,Begin 表示搜索区域内第一个元素所在的位置,Mid 表示中间元素所在的位置。
所有元素的位置分别用 0~9 表示,中间元素的位置为 ⌊ 0 + (9 - 0) / 2 ⌋ = 4,如下图所示:
中间元素 27 < 31,可以断定 [0, 4] 区域内绝对没有 31,目标元素只可能位于 [5, 9] 区域内,如下图所示:
(2)在 [5, 9] 区域内,中间元素的位置为 ⌊ 5 + (9 - 5) / 2 ⌋ = 7,如下图所示:
中间元素 35 > 31,可以断定 [7, 9] 区域内绝对没有 31,目标元素只可能位于 [5,6] 中,如下图所示:
(3)在 [5, 6] 区域内,中间元素的位置为 ⌊ 5 + (6- 5) / 2 ⌋ = 5,中间元素就是 31,成功找到目标元素。
六、代码展示
Python
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = left + (right - left) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# 示例
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
target = 7
result = binary_search(arr, target)
if result != -1:
print(f"元素 {target} 在数组中的索引为: {result}")
else:
print(f"元素 {target} 不在数组中")
C
#include <stdio.h>
//实现二分查找算法,ele 表示要查找的目标元素,[p,q] 指定查找区域
int binary_search(int *arr,int p,int q,int ele) {
int mid = 0;
//如果[p,q] 不存在,返回 -1
if (p > q) {
return -1;
}
// 找到中间元素所在的位置
mid = p + (q - p) / 2;
//递归的出口
if (ele == arr[mid]) {
return mid;
}
//比较 ele 和 arr[mid] 的值,缩小 ele 可能存在的区域
if (ele < arr[mid]) {
//新的搜索区域为 [p,mid-1]
return binary_search(arr, p, mid - 1, ele);
}
else {
//新的搜索区域为 [mid+1,q]
return binary_search(arr, mid + 1, q, ele);
}
}
int main()
{
int arr[10] = { 10,14,19,26,27,31,33,35,42,44 };
//输出二叉查找元素 31 所在位置的下标
printf("%d", binary_search(arr, 0, 9, 31));
return 0;
}
Java
public class Demo {
// 实现二分查找算法,ele 表示要查找的目标元素,[p,q] 指定查找区域
public static int binary_search(int[] arr, int p, int q, int ele) {
// 如果[p,q] 不存在,返回 -1
if (p > q) {
return -1;
}
// 找到中间元素所在的位置
int mid = p + (q - p) / 2;
// 递归的出口
if (ele == arr[mid]) {
return mid;
}
// 比较 ele 和 arr[mid] 的值,缩小 ele 可能存在的区域
if (ele < arr[mid]) {
// 新的搜索区域为 [p,mid-1]
return binary_search(arr, p, mid - 1, ele);
} else {
// 新的搜索区域为 [mid+1,q]
return binary_search(arr, mid + 1, q, ele);
}
}
public static void main(String[] args) {
int[] arr = new int[] { 10, 14, 19, 26, 27, 31, 33, 35, 42, 44 };
// 输出二叉查找元素 31 所在位置的下标
int add = binary_search(arr, 0, 9, 31);
System.out.print(add);
}
}