分治算法之二分搜索-递归与迭代双解法

一、二分搜索基础

1. 适用前提

仅适用于已排序的数组(默认升序,降序需调整比较逻辑)。

2. 核心思想 “分而治之”

  1. 取当前搜索范围的中间元素,与目标值比较。
  2. 若目标值 = 中间元素:找到目标,返回下标。
  3. 若目标值 < 中间元素:目标在左半部分,缩小范围至左半。
  4. 若目标值 > 中间元素:目标在右半部分,缩小范围至右半。
  5. 若搜索范围无效(左边界 > 右边界):目标不存在,返回 - 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

仅需读取数组(如查找、打印)

六、总结

  1. 二分搜索核心是 “分而治之”,前提是数组已排序,时间复杂度均为 O (log n)。
  2. 递归版适合学习理解,代码简洁但有栈溢出风险;迭代版适合工程实践,效率高且稳定。
  3. 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值