定义
二分查找(binary search)是一种在有序数组中查找某一元素的算法,也称为折半搜索或对数搜索。
过程
以在升序数组中查找一个数为例:
- 每次考察数组当前部分的中间元素。
- 如果中间元素是目标元素,搜索结束。
- 如果中间元素小于目标值,只需在右侧查找。
- 如果中间元素大于目标值,只需在左侧查找。
性质
- 时间复杂度:
- 最优时间复杂度:O(1)
- 平均和最坏时间复杂度:O(\log n)
- 空间复杂度:
- 迭代版本:O(1)
- 递归版本:O(\log n)
实现
最大值最小化
如果一个数组的某一部分满足条件,另一部分不满足,这种情况下也可以用二分查找法来找出满足条件的最大或最小值。
STL中的二分查找
C++ 标准库中提供了 std::lower_bound
和 std::upper_bound
,二者均采用二分实现。
bsearch
C标准库中的 bsearch
函数实现了二分查找。
二分答案
在解题时,如果答案满足单调性,可以通过二分法来枚举答案。这称为“二分答案”。
三分法
引入
三分法用于求单峰函数的极值点。
过程
与二分法类似,但每次操作在当前区间 [l, r] 内取两点 lmid 和 rmid。如果 f(lmid) < f(rmid),则在 [rmid, r] 中函数单调递增,可舍去这一区间。
实现
例题
洛谷 P3382 -【模板】三分法
分数规划
分数规划描述的是通过选出若干个物品,使得 \(\frac{\sum{c_i}}{\sum{d_i}}\) 最大或最小的问题。常用二分法解决。
什么是二分查找?
二分查找是一种在有序数组中查找某一特定元素的算法。它通过将数组分成两半,逐步缩小查找范围,从而快速找到目标元素。也称为折半搜索或对数搜索。
二分查找的时间复杂度和空间复杂度是多少?
- 时间复杂度:
- 最优时间复杂度:O(1)
- 平均和最坏时间复杂度:O(\log n)
- 空间复杂度:
- 迭代版本:O(1)
- 递归版本:O(\log n)
如何实现二分查找的迭代版本?
以下是迭代版本的实现代码:
何时使用二分查找法来找出满足条件的最大或最小值?
当满足以下条件时,可以使用二分查找法来找到满足条件的最大或最小值:
- 答案在一个固定区间内。
- 可以容易地判断某个值是否满足条件。
- 可行解在区间内单调变化(即,如果 x 是满足条件的,那么 x + 1 或 x - 1 也满足条件)。
C++ 标准库中有哪些函数实现了二分查找?
C++ 标准库中有以下函数实现了二分查找:
std::lower_bound
:查找首个不小于给定值的元素。std::upper_bound
:查找首个大于给定值的元素。
这两个函数都定义于头文件 <algorithm>
中。
bsearch 函数与 lower_bound 有什么不同?
- bsearch:
- 定义在
<stdlib.h>
或<cstdlib>
中。 - 接受五个参数:待查元素的地址、数组名、元素个数、元素大小、比较函数。
- 返回找到元素的地址,元素类型为
void*
。
- lower_bound:
- 定义在
<algorithm>
中。 - 接受两个迭代器,返回第一个不小于给定值的迭代器。
什么是“二分答案”?
“二分答案”是在解决一些问题时,先枚举答案的范围,然后使用二分查找来确定答案的值。只要答案满足单调性,就可以用二分法来加速求解。
三分法用于解决什么问题?
三分法主要用于求解单峰函数的极值点。它的目标是找到函数在给定区间内的最小值或最大值。
三分法的实现步骤是什么?
三分法的伪代码如下:
在计算 lmid
和 rmid
时,通常设定 mid - ε
和 mid + ε
,以避免溢出并尽量减少操作次数。
分数规划问题如何使用二分法解决?
分数规划问题通常涉及最大化或最小化比率 \(\frac{\sum{c_i}}{\sum{d_i}}\)。使用二分法的步骤如下:
- 设定初值:确定初始区间
[l, r]
。 - 定义函数:设定目标函数 \(f(x) = \frac{\sum{c_i}}{\sum{d_i}}\)。
- 二分查找:在区间内使用二分查找来寻找使目标函数最大或最小的点。
二分查找如何在有序数组中工作?
二分查找在有序数组中工作的过程如下:
- 初始设定:定义数组的起始索引
start
和结束索引end
。 - 查找过程:
- 计算中间索引
mid = start + (end - start) / 2
。 - 比较中间元素
arr[mid]
和目标值key
。 - 如果
arr[mid] == key
,返回mid
。 - 如果
arr[mid] < key
,更新start = mid + 1
。 - 如果
arr[mid] > key
,更新end = mid - 1
。
- 重复以上步骤:直到
start > end
。
二分查找在实际应用中有哪些典型问题?
- 查找元素:在有序数组中查找某个元素的索引。
- 查找插入位置:查找某个元素在有序数组中的插入位置。
- 查找最左或最右位置:在有重复元素的数组中查找某个元素的最左或最右位置。
- 查找临界值:在某个区间内查找使某个函数值满足特定条件的临界值。
如何确保在实现二分查找时防止整数溢出?
使用 mid = start + (end - start) / 2
或 mid = start + ((end - start) >> 1)
来计算中间索引,而不是直接使用 (start + end) / 2
,以防止 start + end
超出整数范围。
std::upper_bound
和 std::lower_bound
有什么实际应用场景?
std::lower_bound
:
- 查找不小于某个值的第一个位置。
- 用于二分查找范围的开始位置。
- 在有序数组中进行插入操作时查找插入位置。
std::upper_bound
:
- 查找大于某个值的第一个位置。
- 用于二分查找范围的结束位置。
- 处理有重复元素的数组时确定范围。
bsearch
函数为什么在 C++ 中较少使用?
- 易用性:
std::lower_bound
和std::upper_bound
提供了更高层次的接口,更易于使用。 - 类型安全:
std::lower_bound
和std::upper_bound
提供类型安全的操作,而bsearch
使用void*
。 - C++ 习惯:C++ 程序员更习惯于使用 STL 提供的函数,这些函数更符合 C++ 的编程风格。
如何判断一个问题是否适合使用“二分答案”?
判断一个问题是否适合使用“二分答案”时需要考虑以下几点:
- 答案范围确定:答案在一个固定的范围内。
- 单调性:如果一个解满足条件,那么更大或更小的解也满足条件。
- 验证条件:可以容易地验证一个解是否满足条件。
三分法与二分法在寻找极值点时有何不同?
- 二分法:用于查找特定值或满足条件的值,适用于单调函数。
- 三分法:用于查找单峰函数的极值点,通过在区间内取两个中间点来比较函数值,缩小查找范围。
三分法中的 eps
参数通常如何选择?
eps
参数用于控制精度,通常选择一个很小的值,如 1e-7
或 1e-9
。具体选择取决于问题的精度要求和计算环境的精度。
分数规划问题中,如何确定分数的上下限?
- 分子和分母的范围:根据分子和分母的最小和最大值来确定初始上下限。
- 合理推测:根据问题的背景和要求,合理推测一个区间。
- 迭代调整:通过迭代和二分查找逐步调整上下限,确保在该范围内找到最优解。
二分法在求解最大值最小值问题中的优势是什么?
- 效率高:每次查找范围减半,时间复杂度为 O(\log n)。
- 实现简单:算法逻辑简单,易于实现。
- 适用范围广:适用于各种单调性问题和边界问题。
- 确定性强:可以精确地找到满足条件的解或范围。