数据结构:数组:二分查找(Binary Search)
目录
什么是二分查找?
查找示例
示例一:在数组中查找 key = 6
示例二:查找失败,key = 7
代码实现
递归版本的二分查找
什么是二分查找?
我们先问自己:
假设我有一个有序数组,我想查找某个数,有没有更快的办法?
例子:一个有序数组
A = [2, 4, 6, 8, 10, 12, 14, 16, 18]
我们要查找数字 10
复习线性查找(原始直觉)
你会从左往右开始:
查 A[0] = 2 → 不对
查 A[1] = 4 → 不对
查 A[2] = 6 → 不对
查 A[3] = 8 → 不对
查 A[4] = 10 ✅ 找到了!
虽然找到了,但我们用了 5 次比较。
❓ 问题:什么信息是线性查找浪费掉的?
数组是升序排序的!
这意味着:
如果你查找的值比当前元素还小,那么后面的所有元素就都不可能是答案!
如果我能一次性排除一大半的元素,会不会更快?
与其从前往后一个个试,
不如——每次查中间值,把数组“砍半”!
这就是二分查找的思想雏形!
查找示例
我们按照“查中间、砍一半”的策略,一步步来模拟:
示例一:在数组中查找 key = 6
Step 1:查中间位置 A[4] = 10
目标 6 < 10
→ 所以,只需要在左半边找(A[0] 到 A[3])
Index: 0 1 2 3 4 5 6 7 8
Value: [2, 4, 6, 8, 10, 12, 14, 16, 18]^ ^ ^low mid high第一次:
mid = (0 + 8) / 2 = 4 → A[4] = 10
6 < 10 → 往左边找 → high = mid - 1 = 3
Step 2:在 [2, 4, 6, 8]
中查中间 A[1] = 4
目标 6 > 4
→ 所以,只需要在右半边(A[2] 到 A[3])
Index: 0 1 2 3
Value: [2, 4, 6, 8]^ ^ ^ low mid high
第二次:mid = (0+3)/2 = 1 → A[1] = 4
6 > 4 → 往右边找 → low = mid + 1 = 2
Step 3:在 [6, 8]
中查中间 A[2] = 6
✅ 找到了!
Index: 2 3
Value: [ 6, 8]^ ^low high^ mid
第三次:mid = (2+3)/2 = 2 → A[2] = 6
✅ 找到!返回索引 2
示例二:查找失败,key = 7
Step 1:A[4] = 10
→ 7 < 10 → 查左半边 [2,4,6,8]
Index: 0 1 2 3 4 5 6 7 8
Value: [2, 4, 6, 8, 10, 12, 14, 16, 18]^ ^ ^low mid high
第一次:
mid = 4 → A[4] = 10
7 < 10 → high = mid - 1 = 3
Step 2:A[1] = 4
→ 7 > 4 → 查右半边 [6,8]
Index: 0 1 2 3
Value: [2, 4, 6, 8]^ ^ ^low mid high
第二次:
mid = 1 → A[1] = 4
7 > 4 → low = mid + 1 = 2
Step 3:A[2] = 6
→ 7 > 6 → 查右边 [8]
Index: 2 3
Value: [6, 8]^ ^low high^ mid = 2 → A[2] = 6
7 > 6 → low = mid + 1 = 3
Step 4:A[3] = 8
→ 7 < 8 → 查左边是空的 ❌
→ 结束 → 没找到!
Index: 3
Value: [8]^low^highmid = 3 → A[3] = 8
7 < 8 → high = mid - 1 = 2
low = 3, high = 2 → ❌ low > high → 查找失败,返回 -1
代码实现
二分查找的本质是“每次查中间元素,利用有序性排除一半”。它通过逐步缩小查找范围,实现快速定位目标。
✅ Step 1:明确变量角色(核心三指针)
int low = 0; // 起始边界
int high = n - 1; // 终止边界
int mid; // 中间位置
这三个变量决定了你当前在哪段区间内搜索
✅ Step 2:写出搜索框架
while (low <= high) {mid = (low + high) / 2;if (A[mid] == key) {return mid; // 找到了!} else if (key < A[mid]) {high = mid - 1; // 去左边找} else {low = mid + 1; // 去右边找}
}
return -1; // 没找到
✅ Step 3:完整 C++ 代码
#include <iostream>
using namespace std;int binarySearch(int A[], int n, int key) {int low = 0, high = n - 1;int mid;while (low <= high) {mid = (low + high) / 2;if (A[mid] == key)return mid;else if (key < A[mid])high = mid - 1;elselow = mid + 1;}return -1; // 没找到
}int main() {int A[] = {2, 4, 6, 8, 10, 12, 14, 16, 18};int n = sizeof(A) / sizeof(A[0]);int key = 6;int index = binarySearch(A, n, key);if (index != -1)cout << "找到了!位置是: " << index << endl;elsecout << "没找到!" << endl;return 0;
}
代码中每一部分的意义
部分 | 作用 |
---|---|
low , high | 控制搜索的范围 |
mid | 当前要比较的位置 |
if (A[mid] == key) | 找到了!直接返回 |
key < A[mid] | 去左半边找(更新 high) |
key > A[mid] | 去右半边找(更新 low) |
while (low <= high) | 只要范围合法就继续查 |
递归版本的二分查找
✅ 一、从你已知的过程出发
我们刚刚已经通过 low
、high
、mid
逐步缩小范围,一步一步查找目标元素。
每一次做的事情是:
-
计算中间元素
mid
-
判断
key
和A[mid]
的关系:-
相等 → 返回索引
-
小于 → 在 左边那一半继续查
-
大于 → 在 右边那一半继续查
-
✅ 二、你观察到了什么?
每次都把问题变成了“在一段更小的数组里做同样的查找”
是不是很眼熟?
问题本身被“缩小”了,但做的事情没有变
这就是递归的核心:
解决问题的方法和子问题的方法是一样的
✅ 三、推导出递归的三个条件(递归三要素)
为了写出递归,你必须找到:
要素 | 对应内容 |
---|---|
递归出口 | 找到 key 或 low > high(查找失败) |
递归调用 | 去左半边或右半边查 |
参数缩小 | 每次都缩小 low 到 high 的范围 |
✅ 四、写下“语言版本”的递归逻辑
我们不写代码,先用中文自然语言表达:
如果 A[mid] == key
,就返回 mid
如果 key < A[mid]
,就在 low ~ mid - 1
这一段继续查
如果 key > A[mid]
,就在 mid + 1 ~ high
这一段继续查
如果 low > high
,说明范围已经空了,返回 -1
你已经推导出了核心逻辑!剩下只是翻译成代码。
📌 一步步实现递归版本二分查找代码
第一步:定义函数签名
你需要的不是只传数组和 key,而是也要传递当前要查找的范围:
int binarySearch(int A[], int low, int high, int key)
第二步:写出递归出口(base case)
if (low > high)return -1; // 查找失败
第三步:写出计算 mid 和判断逻辑
int mid = (low + high) / 2;if (A[mid] == key)return mid;
第四步:递归调用自己(左半 or 右半)
if (key < A[mid])return binarySearch(A, low, mid - 1, key); // 查左边
elsereturn binarySearch(A, mid + 1, high, key); // 查右边
最终递归代码版本如下:
int binarySearch(int A[], int low, int high, int key) {if (low > high)return -1; // 查找失败int mid = (low + high) / 2;if (A[mid] == key)return mid;else if (key < A[mid])return binarySearch(A, low, mid - 1, key);elsereturn binarySearch(A, mid + 1, high, key);
}
未完待续……