引言
在1个升序的数组中查找指定的数字n,很容易想到的办法就是遍历数组,但是这种方法效率较低。此时可以使用二分查找,二分查找适合有序或排序过的数组
目录
引言
什么是二分查找?
引言题目代码
为什么两个公式等价
二分查找有两个限制条件
二分查找的写法
左闭区--右闭区
左闭区--右开区
什么是二分查找?
二分查找(Binary Search),又称折半查找,是一种在有序数组中查找特定元素的高效算法。二分查找的过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则搜索过程将在数组的大于或小于中间元素的那一半区域中继续,以此类推,直到找到要查找的元素,或者剩下的半区域为空。
引言题目代码
#include <stdio.h>int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int left = 0;int right = sizeof(arr) / sizeof(arr[0]) - 1;int key = 7; // 要找的数字int mid = 0; // 记录中间元素的下标int find = 0;while (left <= right) {//注意条件,有=号,否则无论答案对否都会跳出显示找不到mid = (left + right) / 2;if (arr[mid] > key) {right = mid - 1;} else if (arr[mid] < key) {left = mid + 1;} else {find = 1;break;}}if (1 == find) {printf("找到了,下标是%d\n", mid);} else {printf("找不到\n");}return 0;
}
求中间元素的下标, mid = (left+right)/2 ,如果left和right比较大的时候可能存在问 题,可以使用这个公式防止溢出:mid = left+(right-left)/2
这个公式等价是我在学习二分查找的时候最有疑问的一个点。现在我来讲一下
为什么两个公式等价
先假设我们有以下数组和要查找的键值:
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int key = 7;
初始时,left 和 right 分别设置为数组的起始和结束索引:
int left = 0; // 数组的起始索引
int right = sizeof(arr) / sizeof(arr[0]) - 1; // 数组的结束索引
在第一次循环中,我们将计算 mid:
使用原始表达式:
mid = (left + right) / 2;
mid = (0 + 9) / 2;
mid = 9 / 2;
mid = 4;
使用修改后的表达式:
mid = left + (right - left) / 2;
mid = 0 + (9 - 0) / 2;
mid = 0 + 9 / 2;
mid = 0 + 4;
mid = 4;
现在,假设 left 和 right 的值非常大,接近 int 类型的最大值 INT_MAX。如果我们使用原始表达式,left + right 可能会导致溢出:
// 假设 left 和 right 非常接近 INT_MAX
left = INT_MAX - 100;
right = INT_MAX - 50;// 使用原始表达式
mid = (left + right) / 2;
// 如果不发生溢出,结果应该是 (INT_MAX - 100 + INT_MAX - 50) / 2
// 但实际上,left + right 会溢出,导致错误的结果
如果我们使用修改后的表达式,就不会发生溢出,因为 right - left 不会超过 int 类型的最大值:
// 使用修改后的表达式
mid = left + (right - left) / 2;
// 结果是 (INT_MAX - 100) + ((INT_MAX - 50) - (INT_MAX - 100)) / 2
// 这可以安全计算,不会溢出
二分查找有两个限制条件
- 查找的数量只能是一个,不能是多个
- 查找的对象在逻辑上必须是有序的
二分查找的写法
左闭区--右闭区
就是我上边引言的代码
左闭区--右开区
#include <stdio.h>int main() {// 示例数组,实际使用时可以从文件、命令行参数或用户输入获取int nums[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};int target = 4; // 需要查找的目标值int numsSize = sizeof(nums) / sizeof(nums[0]); // 计算数组大小// 初始化左边界和右边界int left = 0;int right = numsSize; // 注意:右边界是开区间,所以是数组大小// 二分查找循环while (left < right) { // 左闭右开,所以使用小于号int mid = left + (right - left) / 2; // 计算中间位置if (nums[mid] == target) {// 找到目标值,返回索引printf("目标值 %d 在数组中的索引为 %d。\n", target, mid);return 0; // 程序结束} else if (nums[mid] < target) {// 目标值在右区间,调整左边界left = mid + 1;} else {// 目标值在左区间,调整右边界right = mid;}}// 未找到目标值printf("目标值 %d 未在数组中找到。\n", target);return 0;
}