当前位置: 首页 > news >正文

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,根据"大了"或"小了"的反馈,再在剩余区间的中间继续猜。

严格前提条件

  1. 数据必须有序(通常为升序排列,降序需调整比较逻辑)
  2. 支持随机访问(数组结构,通过索引访问元素的时间复杂度为O(1))
⚠️ 注意:对链表等不支持随机访问的数据结构,二分查找无法发挥优势(访问中间元素需O(n)时间)

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. 给定有序数组[1,3,5,7,9,11],查找目标值7,手动模拟二分查找过程。
  2. 修改二分查找代码,实现从降序排列的数组中查找目标值。

进阶题

  1. 设计一个函数,在有序数组中查找目标值出现的次数(如[2,2,3,4,4,4,5]中4出现3次)。
  2. 实现一个函数,找出两个有序数组的中位数,要求时间复杂度O(log(m+n))。
练习题3参考答案
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;
}

总结

二分查找是算法设计中分治思想的经典应用,通过将问题规模指数级缩小,实现了高效的查找功能。掌握二分查找不仅要记住代码实现,更要理解其边界处理逻辑区间定义的重要性。在实际应用中,灵活运用二分查找的变体可以解决各种搜索问题,是程序员必备的基础技能之一。

http://www.xdnf.cn/news/1356067.html

相关文章:

  • 机器学习算法-朴素贝叶斯
  • 优化OpenHarmony中lspci命令实现直接获取设备具体型号
  • 机械学习综合练习项目
  • 基于SpringBoot的新能源汽车租赁管理系统【2026最新】
  • Linux 系统管理核心概念与常用命令速查
  • 春秋云镜 Hospital
  • 【Qt开发】常用控件(六)
  • 一个简洁的 C++ 日志模块实现
  • 【数位DP】D. From 1 to Infinity
  • 金山办公的服务端开发工程师-25届春招笔试编程题
  • Python训练营打卡 DAY 45 Tensorboard使用介绍
  • 基于电磁频谱地图的辐射源定位算法复现
  • 基于TimeMixer现有脚本扩展的思路分析
  • 基础IO
  • CryptSIPVerifyIndirectData函数分析
  • 刷题日记0823
  • 环境 (shell) 变量
  • Nacos-12--扩展:@RefreshScope和@ConfigurationProperties实现热更新的原理
  • Kubernetes笔记整合-1
  • 一种通过模板输出Docx的方法
  • LeakyReLU和ReLU的区别
  • 探索 JUC:Java 并发编程的神奇世界
  • KVM虚拟化:提升企业效率的利器
  • 【嵌入式】【搜集】RTOS相关技术信息整理
  • 微信小程序界面常用操作
  • SpringBoot自动装配原理深度解析
  • 电蚊拍的原理及电压电容参数深度解析:从高频振荡到倍压整流的完整技术剖析
  • Trae Solo模式生成一个旅行足迹App
  • 最新短网址源码,防封。支持直连、跳转。 会员无广
  • Azure Kubernetes Service (AKS)