Day 10 - Pre-Knowledge for Binary Search
Day10 - 二分查找前置知识
1. 二分查找介绍
顾名思义二分查找是一种搜索算法(折半查找),常用在有序数组中查找一个目标值。 二分查找将目标值与数组的中间元素进行比较,来决定是在数组的左半边还是右半边查找目标值,然后在符合条件的半边继续进行二分查找,直到找到目标或符合条件的半边为空。使用二分查找可以在O(logn)
时间内完成一个指定元素的搜索。
举个例子,给定一个有序数组arr=[1, 2, 3, 5, 8, 13, 21]
,让我们找到数组中13
所在的位置索引。
最简单的方法就是遍历数组,这样需要将数组中的元素跟目标值比较6
次我们才能得到13
所在的位置索引。

如果使用二分查找,我们首先需要定义两个索引L
指向数组第一个元素,R
指向数组最后一个元素,即L = 0, R = len(arr) - 1 = 6
,我们在[0, 6]
这个区间搜索目标值13
,然后计算数组中间元素的索引Mid = (L + R) / 2 = 3
,此时数组中间元素为arr[3] = 5
。
因为5 < 13
,所以更新L = Mid + 1 = 4
,R = 6
保持不变,我们继续在[4, 6]
这个区间搜索目标值13
。 此时搜索区间的中间元素的索引为 Mid = (L + R) / 2 = 5
,因为arr[5] = 13
,所以我们找到了目标值13
。

整个搜索过程只将数组中元素跟目标值比较了2
次,相比第一种遍历数组(线性搜索),速度提高了3
倍,实际上数组中元素越多,二分查找的查找速度优势越明显。
2. 二分查找实现
有两种方式
2.1. 迭代法
#include <iostream>
using namespace std;
// 二分查找算法
int binarySearch(int arr[], int L, int R, int target)
{
while (L <= R)
{
// 计算中间位置索引
int Mid = (L + R) / 2;
// 如果中间位置元素等于目标值,则返回索引
if(arr[Mid] == target)
{
return Mid;
}
// 如果中间位置元素小于目标值
else if(arr[Mid] < target)
{
// 更新左边界为中间位置索引+1
L = Mid + 1; // 确保L有机会大于R
}
// 如果中间位置元素大于目标值
else if(arr[Mid] > target)
{
// 更新右边界为中间位置索引-1
R = Mid - 1; // 确保R有机会小于L
}
}
// L > R,表示没有找到目标值,返回-1
return -1;
}
int main(void)
{
// 测试代码
int arr[] = {1, 2, 3, 5, 8, 13, 21};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 13;
int result = binarySearch(arr, 0, n - 1, target);
if (result == -1)
{
cout << "Target is not present in array" << endl;
}
else
{
cout << "Target is present at index " << result << endl;
}
return 0;
}
代码输出为:
Target is present at index: 5
时间复杂度: O(logn),其中n
为数组的长度。
空间复杂度: O(1)。
2.2. 递归法
#include <iostream>
using namespace std;
// 递归法:
int binarySearchRecursive(int arr[], int L, int R, int target)
{
// 没有找到目标值,返回-1
if (L > R)
{
return -1;
}
int Mid = (L + R) / 2;
// 如果中间位置元素等于目标值,结束递归,返回索引
if (arr[Mid] == target)
{
return Mid;
}
// 如果中间位置元素小于目标值,继续递归去右边区间[Mid+1, R]
else if (arr[Mid] < target)
{
return binarySearchRecursive(arr, Mid + 1, R, target);
// 实则 Mid + 1 == L
}
// 如果中间位置元素大于目标值,继续递归去左边区间[L, Mid-1]
else if (arr[Mid] > target)
{
return binarySearchRecursive(arr, L, Mid - 1, target);
// 实则 Mid - 1 == R
}
return 0; // 这一句其实可以不加,但是会报出非空函数无返回值的警告......
}
int main(void)
{
// 测试代码
int arr[] = {1, 2, 3, 5, 8, 13, 21};
int n = sizeof(arr) / sizeof(arr[0]);
int target = 13;
int result = binarySearchRecursive(arr, 0, n - 1, target);
if (result == -1)
{
cout << "Target is not present in array" << endl;
}
else
{
cout << "Target is present at index: " << result << endl;
}
return 0;
}
代码输出为:
Target is present at index: 5
时间复杂度:O(logn),其中n
为数组的长度。
空间复杂度:O(logn),因为递归是通过栈来实现的。
3. 注意的点
进行二分查找前,数组中的元素必须是有序的。
二分查找实际上也是用的双指针法,双指针指向的应该是数组中的元素,所以在实现二分查找的时候索引
R
最好是指向数组的最后一个元素,即R = len(arr) - 1
。当然也有些同学习惯把索引R
指向数组越界后的一个元素,即R = len(arr)
。两种方式在搜索过程中跳出搜索的条件有些差异(L <= R
和L < R
之间的差异),只要能获得正确答案都可以,不过国内外的算法教科书上还是采用了第一种R = len(arr) - 1
。这里只是给大家一个提示,不至于看到其他写法一脸懵,习惯于一种方式就好了,在代码实现的时候可以在纸上模拟一个简单的例子,确保边界条件都考虑到了。要注意边界情况的处理,考虑数组中只有一个元素或两个元素需不需要特殊处理,以及索引L,R的移动条件。
4. 二分查找应用
二分查找可以用作机器学习中算法构建,例如查找模型的最佳超参数。
可用于计算机图形学中的搜索,例如光线追踪或纹理映射的算法。
可以用来查找数据库。
Last updated