一、二分搜索基础
1. 适用前提
仅适用于已排序的数组(默认升序,降序需调整比较逻辑)。
2. 核心思想 “分而治之”
- 取当前搜索范围的中间元素,与目标值比较。
- 若目标值 = 中间元素:找到目标,返回下标。
- 若目标值 < 中间元素:目标在左半部分,缩小范围至左半。
- 若目标值 > 中间元素:目标在右半部分,缩小范围至右半。
- 若搜索范围无效(左边界 > 右边界):目标不存在,返回 - 1。
二、递归版二分搜索
1. 核心要素
- 递归函数参数:待搜索数组、目标值、当前左边界(left)、当前右边界(right)。
- 递归终止条件:① 找到目标(返回下标);② 范围无效(返回 - 1)。
2. C++ 实现代码
#include <iostream>
#include <vector>
using namespace std;
// 递归函数:在[left, right]范围内搜索target
int binarySearchRecursive(const vector<int>& arr, int target, int left, int right) {
// 终止条件1:范围无效,目标不存在
if (left > right) return -1;
// 计算中间下标(避免溢出:等价于(left+right)/2)
int mid = left + (right - left) / 2;
// 终止条件2:找到目标,返回下标
if (arr[mid] == target) return mid;
// 目标在左半部分:递归搜索[left, mid-1]
else if (arr[mid] > target)
return binarySearchRecursive(arr, target, left, mid - 1);
// 目标在右半部分:递归搜索[mid+1, right]
else
return binarySearchRecursive(arr, target, mid + 1, right);
}
// 测试代码
int main() {
vector<int> sortedArr = {2,5,8,12,16,23,38};
int target;
cout << "输入目标值:";
cin >> target;
// 初始调用:范围为整个数组[0, 数组长度-1]
int res = binarySearchRecursive(sortedArr, target, 0, sortedArr.size()-1);
if (res != -1)
cout << "目标下标:" << res << endl;
else
cout << "目标不存在" << endl;
return 0;
}
3. 复杂度分析
- 时间复杂度:O (log n)(每次范围缩小一半,递归层数为 log₂n)
- 空间复杂度:O (log n)(递归调用栈深度 = 递归层数)
4. 注意事项
- 避免栈溢出:数组过大(如长度 10⁶)时,递归层数可能超过系统栈深度,导致崩溃。
- 中间下标计算:优先用left + (right - left)/2,而非(left+right)/2,防止整数溢出。
三、迭代版二分搜索
1. 核心逻辑
通过while循环手动维护搜索范围,无需递归调用,用变量记录中间下标(mid)。
2. C++ 实现代码
#include <iostream>
#include <vector>
using namespace std;
// 迭代函数:搜索整个数组
int binarySearchIterative(const vector<int>& arr, int target) {
int left = 0; // 初始左边界
int right = arr.size() - 1; // 初始右边界
// 循环条件:范围有效(left <= right)
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid; // 找到目标
else if (arr[mid] > target) right = mid - 1; // 缩小右边界
else left = mid + 1; // 扩大左边界
}
return -1; // 范围无效,目标不存在
}
// 测试代码
int main() {
vector<int> sortedArr = {2,5,8,12,16,23,38};
int target;
cout << "输入目标值:";
cin >> target;
int res = binarySearchIterative(sortedArr, target);
if (res != -1)
cout << "目标下标:" << res << endl;
else
cout << "目标不存在" << endl;
return 0;
}
3. 复杂度分析
- 时间复杂度:O (log n)(循环次数 = 范围缩小次数,与递归版一致)
- 空间复杂度:O (1)(仅用 left、right、mid 三个变量,无额外栈开销)
四、递归版与迭代版对比
|
对比维度 |
递归版 |
迭代版 |
|
实现方式 |
函数自身调用,依赖调用栈 |
while 循环,手动维护范围 |
|
内存开销 |
O (log n)(调用栈空间) |
O (1)(仅局部变量) |
|
执行效率 |
低(函数调用有栈帧创建 / 销毁开销) |
高(无函数调用开销) |
|
栈溢出风险 |
有(数组过大时) |
无 |
|
代码可读性 |
高(符合分治直观逻辑,代码简洁) |
中(需手动控制边界,新手易出错) |
|
调试难度 |
高(需跟踪多层栈帧) |
低(变量状态清晰,循环内可观察) |
|
适用场景 |
小规模数据、教学演示 |
大规模数据、工程实践(如索引查找) |
五、关键语法解析
1. const vector<int>& arr 含义拆解
|
语法部分 |
作用说明 |
|
vector<int> |
STL 动态数组容器,存储 int 类型数据 |
|
& |
引用(别名),避免拷贝数组,提升效率 |
|
const |
常量修饰,限制通过引用修改数组,保护数据安全 |
2. 为什么用 const vector<int>& arr ?
- 效率:引用传递无需拷贝数组(若数组大,拷贝会消耗大量内存和时间)。
- 安全:const防止函数内部意外修改原数组(二分搜索仅需读数据,无需写)。
- 清晰:明确接口意图 ——“此函数仅读取数组,不修改数组”,便于他人理解代码。
3. 三种数组传递方式对比
|
传递方式 |
是否拷贝 |
是否可修改原数组 |
适用场景 |
|
vector<int> arr |
是 |
否 |
需操作数组副本,不影响原数组 |
|
vector<int>& arr |
否 |
是 |
需修改原数组(如排序函数) |
|
const vector<int>& arr |
否 |
否 |
仅需读取数组(如查找、打印) |
六、总结
- 二分搜索核心是 “分而治之”,前提是数组已排序,时间复杂度均为 O (log n)。
- 递归版适合学习理解,代码简洁但有栈溢出风险;迭代版适合工程实践,效率高且稳定。
- C++ 中传递只读数组时,const vector<int>& arr是 “高效 + 安全” 的最佳写法。
编译报错问题解决
若编译代码出现
[Error] in C++98 'sortedArr' must be initialized by constructor, not by '{...}'
是因为你使用了 C++11 及以上版本的初始化语法(列表初始化 { }),但编译器默认以 C++98 标准编译,而 C++98 不支持这种初始化方式。
解决方法:修改 vector 的初始化方式(兼容 C++98)
将 vector 的初始化代码从 列表初始化 改为 push_back 逐个添加元素,或显式调用构造函数初始化。
// 方法1:先定义空vector,再用push_back添加元素
vector<int> sortedArr;
sortedArr.push_back(2);
sortedArr.push_back(5);
sortedArr.push_back(8);
sortedArr.push_back(12);
sortedArr.push_back(16);
sortedArr.push_back(23);
sortedArr.push_back(38);
// 方法2:用数组初始化
int temp[] = {2, 5, 8, 12, 16, 23, 38};
vector<int> sortedArr(temp, temp + sizeof(temp)/sizeof(temp[0]));
完整代码:
递归法:
#include <iostream>
#include <vector>
using namespace std;
// 递归函数:在[left, right]范围内搜索target
int binarySearchRecursive(const vector<int>& arr, int target, int left, int right) {
// 终止条件1:范围无效,目标不存在
if (left > right) return -1;
// 计算中间下标(避免溢出:等价于(left+right)/2)
int mid = left + (right - left) / 2;
// 终止条件2:找到目标,返回下标
if (arr[mid] == target) return mid;
// 目标在左半部分:递归搜索[left, mid-1]
else if (arr[mid] > target)
return binarySearchRecursive(arr, target, left, mid - 1);
// 目标在右半部分:递归搜索[mid+1, right]
else
return binarySearchRecursive(arr, target, mid + 1, right);
}
// 测试代码
int main() {
int temp[] = {2, 5, 8, 12, 16, 23, 38};
vector<int> sortedArr(temp, temp + sizeof(temp)/sizeof(temp[0]));
int target;
cout << "输入目标值:";
cin >> target;
// 初始调用:范围为整个数组[0, 数组长度-1]
int res = binarySearchRecursive(sortedArr, target, 0, sortedArr.size()-1);
if (res != -1)
cout << "目标下标:" << res << endl;
else
cout << "目标不存在" << endl;
return 0;
}
迭代法:
#include <iostream>
#include <vector>
using namespace std;
// 迭代函数:搜索整个数组
int binarySearchIterative(const vector<int>& arr, int target) {
int left = 0; // 初始左边界
int right = arr.size() - 1; // 初始右边界
// 循环条件:范围有效(left <= right)
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) return mid; // 找到目标
else if (arr[mid] > target) right = mid - 1; // 缩小右边界
else left = mid + 1; // 扩大左边界
}
return -1; // 范围无效,目标不存在
}
// 测试代码
int main() {
int temp[] = {2, 5, 8, 12, 16, 23, 38};
vector<int> sortedArr(temp, temp + sizeof(temp)/sizeof(temp[0]));
int target;
cout << "输入目标值:";
cin >> target;
int res = binarySearchIterative(sortedArr, target);
if (res != -1)
cout << "目标下标:" << res << endl;
else
cout << "目标不存在" << endl;
return 0;
}

3586

被折叠的 条评论
为什么被折叠?



