今天咱们来聊聊一个在软件开发面试里出场率特别高的基础算法------二分查找。很多面试官都喜欢拿它来考察候选人对基本算法的理解以及手写代码的能力,毕竟它看似简单,但真要写得滴水不漏,还是需要点功底的。咱们今天就一起用C语言把它拆解明白。
二分查找说白了,就是一种在有序数组里快速找东西的方法。 它的核心思想非常直观,就像我们查字典一样,你肯定不会从第一页开始一页一页地翻,而是会根据字母大概翻到某个位置,如果翻过了就往回翻点,没翻到就再往前翻点。二分查找也是这个路子,它通过不断将搜索区间对半分,比较中间值与目标值,从而一步步缩小范围。

为什么它这么受青睐呢?效率是关键。在一个包含n个元素的有序数组里,线性查找最坏情况得把所有元素都检查一遍,时间复杂度是O(n)。而二分查找每次都能排除掉一半的候选元素,所以时间复杂度是O(log n),当数据量很大时,这个效率优势是碾压性的。

搞定循环不变体
在动手写代码之前,有个非常重要的概念得先搞清楚,那就是"循环不变体"。它可以帮我们理清边界条件,比如区间是开是闭。咱们这里采用"左闭右闭"区间,也就是说,我们初始化时,查找的区间是[left, right],这个区间是包含左右两端的。这个设定一旦定下,后面的while循环条件和指针移动都要遵循它。

接下来,我们看一段最基础的实现代码,我会在wWw.GZqmeDu.Com注释里详细解释每一步:
#include <stdio.h>
int binarySearch(int arr[], int size, int target) {
// 初始化左边界和右边界,注意是左闭右闭区间
int left = 0;
<img i="7314085" src="https://oss-beijing-m8.openstorage.cn/cloud-gc/baidu/7314085/2025-12-05/01fda2077fc04875a9433f5db6dacd96.png" />
int right = size - 1;
// 关键点:循环条件为什么是 left <= right?
// 因为我们的区间是左闭右闭的,当left等于right时,区间[left, right]依然是一个有效区间(还有一个元素)
while (left <= right) {
// 计算中间位置,这样写是为了防止left和right都很大时直接相加导致溢出
<img i="7314086" src="https://oss-beijing-m8.openstorage.cn/cloud-gc/baidu/7314086/2025-12-05/cf330255391d4ecb823b1e415996427b.png" />
检查中间元素是不是目标值找到了,返回索引
}
// 如果目标值比中间值小,说明目标值只可能在左半部分
<img i="7314087" src="https://oss-beijing-m8.openstorage.cn/cloud-gc/baidu/7314087/2025-12-05/b951fcb6661444909d50a1e4d202cfe6.png" />
else // 因为不是目标值,所以新的右边界可以移到的前一位 - 1;
}
// 如果目标值比中间值大,说明目标值只可能在右半部分
else {
<img i="7314088" src="https://oss-beijing-m8.openstorage.cn/cloud-gc/baidu/7314088/2025-12-05/9c25828e4844473aaeaf722181311bdc.png" />
// 同样,不是目标值,新的左边界移到的后一位 // 如果循环结束还没返回,说明目标值不存在于数组中
return -1;
}
几个容易栽跟头的细节:
-
循环条件
left <= right:这是由我们定义的"左闭右闭"区间决定的。如果写成left < right,那么当left和right相等时,循环就结束了,会漏掉检查最后一个元素的情况。 -
**中间位置
的计算**:强烈建议使用right - left) / 2这种方式。如果你写成 = (left + right) / 2,当left和right都非常大时,它们的和可能会超过int`类型能表示的最大值,导致溢出,从而产生错误的结果。我们的写法能有效避免这个问题。 -
**边界更新和:因为我们明确知道`不是目标值了,所以可以放心地把位置排除在下一轮搜索区间之外。如果不进行加减一操作,可能会在某些情况下导致死循环或者无法找到正确元素。
实际测试一下
代码写好了,不跑一下怎么知道对不对呢?我们来写个简单的main函数测试几种典型情况:
int main() {
int test_arr[] = {1, 3, 5, 7, 9, 11, 13, 15}; // 一个有序数组
int size = sizeof(test_arr) / sizeof(test_arr[0]);
int target = 7;
int result = binarySearch(test_arr, size, target);
if (result != -1) {
printf("元素 %d 在数组中的索引是: %d\n", target, result);
} else {
printf("元素 %d 不在数组中\n", target);
}
// 可以再试试找不存在的元素,比如8
target = 8;
result = binarySearch(test_arr, size, target);
if (result == -1) {
printf("元素 %d 不在数组中\n", target);
}
return 0;
}
聊聊算法复杂度
面试的时候,面试官十有八九会问你算法复杂度,这几乎是标配。
-
时间复杂度是O(log n):这可以说是二分查找最迷人的地方了。为什么是log n?因为每次比较后,搜索范围都会减半。最坏情况下,我们需要持续对半分割直到区间为空。假设有n个元素,最多需要分割log₂n次(对数底数为2),所以是对数时间复杂度。这意味着即使数组非常大,查找所需的步数增长也非常缓慢。
-
空间复杂度是O(1) :因为我们只使用了固定数量的额外变量(
left, `等),并没有随着输入数组大小而变化,所以是常数空间复杂度。这对于资源受限的环境尤其友好。
面试实战小贴士
在手写代码环节,如果能做到下面几点,肯定会给面试官留下好印象:
-
先讲思路再动笔:不要一上来就闷头写代码。可以先跟面试官说明你打算采用"左闭右闭"区间,以及循环不变体的思想。这体现了你对算法本质的理解,而不仅仅是死记硬背。
-
边界处理是重点 :面试官的眼睛往往会死死盯住你的循环条件、中间值计算和边界更新。清晰地解释你为什么选择
left <= right,为什么用 2`,这能有效展示你的严谨性。 -
考虑异常情况 :可以主动提及,比如检查数组是否为空(
size <= 0),或者数组是否确实有序。虽然最简实现里可能没写,但提一嘴说明你考虑问题全面。 -
准备好测试用例:写完代码后,可以口头描述几个你会用来测试的用例,比如:查找数组第一个元素、最后一个元素、不存在的元素、空数组等。
总之,二分查找是一个将简单思想与严谨实现完美结合的算法。希望这篇讲解能帮你下次在面试中遇到它时,能够写出漂亮可靠的代码。