30分钟通关二分查找:C语言实现+LeetCode真题
你将学到什么
- 二分查找的核心原理与适用场景
- 如何用C语言实现标准二分查找算法
- 处理边界条件的关键技巧
- 常见错误与调试方法
- 实战案例分析与性能优化
为什么需要二分查找
想象你在一本1000页的字典中查找"algorithm"这个单词。如果从第一页开始逐页翻找(线性查找),在最坏情况下需要翻1000页。而二分查找通过每次排除一半的可能性,最多只需10次(2^10=1024)就能找到目标,这就是对数级效率的魅力。
核心优势:将线性查找O(n)的时间复杂度优化为O(log n),在大数据量下性能提升显著。例如:
- 当n=100万时,线性查找平均需50万次比较,二分查找仅需20次
- 当n=1亿时,二分查找也只需27次比较
算法核心原理
基本思想
二分查找(Binary Search)基于分治策略,通过不断缩小搜索区间来定位目标元素。类比猜数字游戏:当主持人说"猜一个1-100的数字",最有效的策略是先猜50,根据"大了"或"小了"的反馈,再在剩余区间的中间继续猜。
严格前提条件
- 数据必须有序(通常为升序排列,降序需调整比较逻辑)
- 支持随机访问(数组结构,通过索引访问元素的时间复杂度为O(1))
C语言实现步骤
1. 函数定义
#include <stdio.h>// 功能:在有序数组中查找目标值
// 参数:arr-有序数组,n-数组长度,target-目标值
// 返回:找到则返回索引,未找到返回-1
int binarySearch(int arr[], int n, int target) {int left = 0; // 左边界int right = n - 1; // 右边界while (left <= right) { // 循环条件:区间有效int mid = left + (right - left) / 2; // 计算中间索引(避免溢出)if (arr[mid] == target) {return mid; // 找到目标,返回索引} else if (arr[mid] < target) {left = mid + 1; // 目标在右半区间,移动左边界} else {right = mid - 1;// 目标在左半区间,移动右边界}}return -1; // 未找到目标
}
2. 关键代码解析
中间索引计算
int mid = left + (right - left) / 2;
// 等价于 (left + right) / 2,但可避免left+right溢出
// 例如:left=1e9, right=1e9时,(left+right)会超过int上限导致溢出
循环条件
while (left <= right)
// 当left > right时,区间[left, right]为空,说明没有找到目标
// 注意:如果使用left < right,可能会漏掉left=right时的检查
边界调整
left = mid + 1; // 目标在右半区间,当前mid不是目标,故+1
right = mid - 1; // 目标在左半区间,当前mid不是目标,故-1
3. 完整测试程序
int main() {int arr[] = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};int n = sizeof(arr) / sizeof(arr[0]);int target = 23;int result = binarySearch(arr, n, target);if (result != -1) {printf("元素 %d 找到,索引为 %d\n", target, result);} else {printf("元素 %d 未找到\n", target);}return 0;
}
执行过程可视化
以查找[2,5,8,12,16,23,38,56,72,91]
中的23为例:
初始区间:left=0, right=9 (索引范围)
mid = 0 + (9-0)/2 = 4 → arr[4]=16 < 23 → 目标在右半区间
left调整为mid+1=5第2次循环:left=5, right=9
mid = 5 + (9-5)/2 = 7 → arr[7]=56 > 23 → 目标在左半区间
right调整为mid-1=6第3次循环:left=5, right=6
mid = 5 + (6-5)/2 = 5 → arr[5]=23 == 23 → 找到目标,返回索引5
💡 技巧:通过在代码中添加printf语句打印每次循环的left、right、mid值,可以直观理解区间变化过程常见错误与调试
错误1:中间值计算溢出
// 错误写法
int mid = (left + right) / 2;
// 当left和right都是大整数时,left+right可能超过int范围导致溢出
错误2:循环条件错误
// 错误写法
while (left < right) { ... }
// 当目标位于left=right位置时,循环不会执行,导致漏查
错误3:边界调整错误
// 错误写法
left = mid; // 或 right = mid;
// 可能导致死循环(如left=1, right=2, mid=1时)
实战案例分析
案例1:查找第一个大于等于目标的元素
// 变体:找到第一个>=target的元素索引
int findFirstGe(int arr[], int n, int target) {int left = 0, right = n - 1;int result = n; // 默认返回n表示所有元素都小于targetwhile (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] >= target) {result = mid; // 记录可能的结果right = mid - 1; // 继续向左查找更小的符合条件元素} else {left = mid + 1;}}return result;
}
案例2:在旋转排序数组中查找
// 变体:在[4,5,6,7,0,1,2]等旋转数组中查找目标
int searchRotate(int arr[], int n, int target) {int left = 0, right = n - 1;while (left <= right) {int mid = left + (right - left) / 2;if (arr[mid] == target) return mid;// 左半部分有序if (arr[left] <= arr[mid]) {if (arr[left] <= target && target < arr[mid]) {right = mid - 1;} else {left = mid + 1;}} else { // 右半部分有序if (arr[mid] < target && target <= arr[right]) {left = mid + 1;} else {right = mid - 1;}}}return -1;
}
性能分析
时间复杂度
- 最佳情况:O(1)(目标恰为中间元素)
- 最坏情况:O(log n)(需查找到最后一个可能位置)
- 平均情况:O(log n)
空间复杂度
- 迭代实现:O(1)(仅使用常数额外空间)
- 递归实现:O(log n)(递归调用栈深度)
练习题
基础题
- 给定有序数组
[1,3,5,7,9,11]
,查找目标值7,手动模拟二分查找过程。 - 修改二分查找代码,实现从降序排列的数组中查找目标值。
进阶题
- 设计一个函数,在有序数组中查找目标值出现的次数(如
[2,2,3,4,4,4,5]
中4出现3次)。 - 实现一个函数,找出两个有序数组的中位数,要求时间复杂度O(log(m+n))。
int countOccurrences(int arr[], int n, int target) {// 找到第一个出现的位置int first = findFirstGe(arr, n, target);if (first == n || arr[first] != target) return 0;// 找到第一个大于target的位置int last = findFirstGe(arr, n, target + 1);return last - first;
}
总结
二分查找是算法设计中分治思想的经典应用,通过将问题规模指数级缩小,实现了高效的查找功能。掌握二分查找不仅要记住代码实现,更要理解其边界处理逻辑和区间定义的重要性。在实际应用中,灵活运用二分查找的变体可以解决各种搜索问题,是程序员必备的基础技能之一。