定义

二分查找(binary search)是一种在有序数组中查找某一元素的算法,也称为折半搜索或对数搜索。

过程

以在升序数组中查找一个数为例:

  1. 每次考察数组当前部分的中间元素。
  2. 如果中间元素是目标元素,搜索结束。
  3. 如果中间元素小于目标值,只需在右侧查找。
  4. 如果中间元素大于目标值,只需在左侧查找。
性质
  • 时间复杂度
  • 最优时间复杂度:O(1)
  • 平均和最坏时间复杂度:O(\log n)
  • 空间复杂度
  • 迭代版本:O(1)
  • 递归版本:O(\log n)
实现
int binary_search(int start, int end, int key) {
  int ret = -1;  // 未搜索到数据返回-1下标
  int mid;
  while (start <= end) {
    mid = start + ((end - start) >> 1);  // 直接平均可能会溢出,所以用这个算法
    if (arr[mid] < key)
      start = mid + 1;
    else if (arr[mid] > key)
      end = mid - 1;
    else {  // 最后检测相等是因为多数搜索情况不是大于就是小于
      ret = mid;
      break;
    }
  }
  return ret;  // 单一出口
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
最大值最小化

如果一个数组的某一部分满足条件,另一部分不满足,这种情况下也可以用二分查找法来找出满足条件的最大或最小值。

STL中的二分查找

C++ 标准库中提供了 std::lower_boundstd::upper_bound,二者均采用二分实现。

bsearch

C标准库中的 bsearch 函数实现了二分查找。

二分答案

在解题时,如果答案满足单调性,可以通过二分法来枚举答案。这称为“二分答案”。

三分法
引入

三分法用于求单峰函数的极值点。

过程

与二分法类似,但每次操作在当前区间 [l, r] 内取两点 lmid 和 rmid。如果 f(lmid) < f(rmid),则在 [rmid, r] 中函数单调递增,可舍去这一区间。

实现
while (r - l > eps) {
  mid = (l + r) / 2;
  lmid = mid - eps;
  rmid = mid + eps;
  if (f(lmid) < f(rmid))
    r = mid;
  else
    l = mid;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
例题

洛谷 P3382 -【模板】三分法

分数规划

分数规划描述的是通过选出若干个物品,使得 \(\frac{\sum{c_i}}{\sum{d_i}}\) 最大或最小的问题。常用二分法解决。


什么是二分查找?

二分查找是一种在有序数组中查找某一特定元素的算法。它通过将数组分成两半,逐步缩小查找范围,从而快速找到目标元素。也称为折半搜索或对数搜索。

二分查找的时间复杂度和空间复杂度是多少?
  • 时间复杂度
  • 最优时间复杂度:O(1)
  • 平均和最坏时间复杂度:O(\log n)
  • 空间复杂度
  • 迭代版本:O(1)
  • 递归版本:O(\log n)
如何实现二分查找的迭代版本?

以下是迭代版本的实现代码:

int binary_search(int start, int end, int key) {
  int ret = -1;  // 未搜索到数据返回-1下标
  int mid;
  while (start <= end) {
    mid = start + ((end - start) >> 1);  // 防止溢出
    if (arr[mid] < key)
      start = mid + 1;
    else if (arr[mid] > key)
      end = mid - 1;
    else {  // 找到目标元素
      ret = mid;
      break;
    }
  }
  return ret;  // 单一出口
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
何时使用二分查找法来找出满足条件的最大或最小值?

当满足以下条件时,可以使用二分查找法来找到满足条件的最大或最小值:

  1. 答案在一个固定区间内
  2. 可以容易地判断某个值是否满足条件
  3. 可行解在区间内单调变化(即,如果 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> 中。
  • 接受两个迭代器,返回第一个不小于给定值的迭代器。
什么是“二分答案”?

“二分答案”是在解决一些问题时,先枚举答案的范围,然后使用二分查找来确定答案的值。只要答案满足单调性,就可以用二分法来加速求解。

三分法用于解决什么问题?

三分法主要用于求解单峰函数的极值点。它的目标是找到函数在给定区间内的最小值或最大值。

三分法的实现步骤是什么?

三分法的伪代码如下:

while (r - l > eps) {
  mid = (l + r) / 2;
  lmid = mid - eps;
  rmid = mid + eps;
  if (f(lmid) < f(rmid))
    r = mid;
  else
    l = mid;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

在计算 lmidrmid 时,通常设定 mid - εmid + ε,以避免溢出并尽量减少操作次数。

分数规划问题如何使用二分法解决?

分数规划问题通常涉及最大化或最小化比率 \(\frac{\sum{c_i}}{\sum{d_i}}\)。使用二分法的步骤如下:

  1. 设定初值:确定初始区间 [l, r]
  2. 定义函数:设定目标函数 \(f(x) = \frac{\sum{c_i}}{\sum{d_i}}\)。
  3. 二分查找:在区间内使用二分查找来寻找使目标函数最大或最小的点。
二分查找如何在有序数组中工作?

二分查找在有序数组中工作的过程如下:

  1. 初始设定:定义数组的起始索引 start 和结束索引 end
  2. 查找过程
  • 计算中间索引 mid = start + (end - start) / 2
  • 比较中间元素 arr[mid] 和目标值 key
  • 如果 arr[mid] == key,返回 mid
  • 如果 arr[mid] < key,更新 start = mid + 1
  • 如果 arr[mid] > key,更新 end = mid - 1
  1. 重复以上步骤:直到 start > end
二分查找在实际应用中有哪些典型问题?
  1. 查找元素:在有序数组中查找某个元素的索引。
  2. 查找插入位置:查找某个元素在有序数组中的插入位置。
  3. 查找最左或最右位置:在有重复元素的数组中查找某个元素的最左或最右位置。
  4. 查找临界值:在某个区间内查找使某个函数值满足特定条件的临界值。
如何确保在实现二分查找时防止整数溢出?

使用 mid = start + (end - start) / 2mid = start + ((end - start) >> 1) 来计算中间索引,而不是直接使用 (start + end) / 2,以防止 start + end 超出整数范围。

std::upper_boundstd::lower_bound 有什么实际应用场景?
  • std::lower_bound
  • 查找不小于某个值的第一个位置。
  • 用于二分查找范围的开始位置。
  • 在有序数组中进行插入操作时查找插入位置。
  • std::upper_bound
  • 查找大于某个值的第一个位置。
  • 用于二分查找范围的结束位置。
  • 处理有重复元素的数组时确定范围。
bsearch 函数为什么在 C++ 中较少使用?
  • 易用性std::lower_boundstd::upper_bound 提供了更高层次的接口,更易于使用。
  • 类型安全std::lower_boundstd::upper_bound 提供类型安全的操作,而 bsearch 使用 void*
  • C++ 习惯:C++ 程序员更习惯于使用 STL 提供的函数,这些函数更符合 C++ 的编程风格。
如何判断一个问题是否适合使用“二分答案”?

判断一个问题是否适合使用“二分答案”时需要考虑以下几点:

  1. 答案范围确定:答案在一个固定的范围内。
  2. 单调性:如果一个解满足条件,那么更大或更小的解也满足条件。
  3. 验证条件:可以容易地验证一个解是否满足条件。
三分法与二分法在寻找极值点时有何不同?
  • 二分法:用于查找特定值或满足条件的值,适用于单调函数。
  • 三分法:用于查找单峰函数的极值点,通过在区间内取两个中间点来比较函数值,缩小查找范围。
三分法中的 eps 参数通常如何选择?

eps 参数用于控制精度,通常选择一个很小的值,如 1e-71e-9。具体选择取决于问题的精度要求和计算环境的精度。

分数规划问题中,如何确定分数的上下限?
  1. 分子和分母的范围:根据分子和分母的最小和最大值来确定初始上下限。
  2. 合理推测:根据问题的背景和要求,合理推测一个区间。
  3. 迭代调整:通过迭代和二分查找逐步调整上下限,确保在该范围内找到最优解。
二分法在求解最大值最小值问题中的优势是什么?
  • 效率高:每次查找范围减半,时间复杂度为 O(\log n)。
  • 实现简单:算法逻辑简单,易于实现。
  • 适用范围广:适用于各种单调性问题和边界问题。
  • 确定性强:可以精确地找到满足条件的解或范围。