二分查找(Binary Search)是一种在有序数组中查找特定元素的高效算法。它的基本思想是通过不断将搜索范围减半来快速定位目标元素,时间复杂度为O(log n),远优于线性查找的O(n)。
二分法不仅用于查找,还广泛应用于求解各种数学和计算问题,如求方程的近似解、寻找最优解等。在计算机科学中,二分查找是最基础且最重要的算法之一,几乎所有程序员都需要熟练掌握。
二分查找的工作原理可以概括为以下步骤:
这种"分而治之"的策略使得每次比较都能排除一半的候选元素,因此效率极高。
以下是二分查找的标准C++实现:
#include
#include
using namespace std;
int binarySearch(const vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 未找到
}
int main() {
vector nums = {1, 3, 5, 7, 9, 11, 13};
int target = 7;
int result = binarySearch(nums, target);
if (result != -1) {
cout << "元素 " << target << " 在索引 " << result << " 处" << endl;
} else {
cout << "元素 " << target << " 未找到" << endl;
}
return 0;
}
这段代码实现了标准的二分查找算法,注意计算mid时使用left + (right - left)/2而非(left + right)/2,可以防止整数溢出。
实际应用中,我们经常需要处理更复杂的情况,以下是几种常见的二分查找变种:
int firstOccurrence(const vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
if (nums[mid] == target) result = mid;
} else {
left = mid + 1;
}
}
return result;
}
lastOccurrence(const vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int result = -1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] <= target) {
left = mid + 1;
if (nums[mid] == target) result = mid;
} else {
right = mid - 1;
}
}
return result;
}
int lowerBound(const vector& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int result = nums.size(); // 默认返回数组长度
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
二分查找最容易出错的地方就是边界条件的处理。常见问题包括:
计算mid时,(left + right) / 2可能导致整数溢出,应该使用left + (right - left) / 2。
在有重复元素的数组中,标准二分查找不能保证返回第一个或最后一个匹配项,需要使用变种算法。
int searchRotated(const vector& nums, int target) {
int left = 0, right = nums.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
// 左半部分有序
if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 右半部分有序
else {
if (nums[mid] < target && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
findPeakElement(const vector& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] < nums[mid + 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
二分查找的时间复杂度为O(log n),空间复杂度为O(1)。在实际应用中,可以通过以下方式优化:
对于理论上无限大的有序序列,可以通过指数搜索(Exponential Search)结合二分查找来定位元素。
对于行列均有序的二维矩阵,可以使用特殊的二分查找策略。
searchMatrix(const vector>& matrix, int target) {
if (matrix.empty() || matrix[0].empty()) return false;
int m = matrix.size(), n = matrix[0].size();
int left = 0, right = m * n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
int midValue = matrix[mid / n][mid % n];
if (midValue == target) {
return true;
} else if (midValue < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return false;
}
二分查找是一种强大而高效的算法,掌握它对于每个程序员都至关重要。学习建议:
通过不断练习和思考,你将能够灵活运用二分法解决各种复杂问题,提升算法能力和编程效率。
附录:推荐练习题
洛谷 P1571
眼红的Medusa
洛谷 P1296
奶牛的耳语
洛谷 P1678
烦恼的高考志愿
P1873
[COCI 2011/2012 #5] EKO / 砍树