二分查找----时间复杂度O(log n)
要求:1.数据有序 2.存储结构是顺序结构(数据可以用下标随机访问)
下面举个例子:
#include<stdio.h>
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
int sz=sizeof(arr)/sizeof(arr[0]);
int target=8;
int left=0,right=sz-1;
while(left<=right)//问题1:"<="和"<"有什么区别,分别在哪些情况使用?
{
int mid=(left+right)/2;
//问题2:(left+right)严谨吗?为什么?
//问题3:mid=(left+right+1)/2与它有什么区别?
if(arr[mid]<target)
left=mid+1;//问题4:left=mid与它有什么区别,分别在哪些情况使用?
else if(arr[mid]>target)
right=mid-1;//问题5:right=mid与它有什么区别,分别在哪些情况使用?
else
{
printf("找到了\n");
break;
}
}
if(left>right)
printf("没找到\n");
return 0;
}
当我们写二分查找的时候,绝大多数人都会被这几个问题搞得头昏,尤其是问题1和问题3,很多人都是这个试试,那个试试,只求过了就行,不清楚为什么要这么写,它们之间的内部逻辑是什么,今天,我就来帮大家解决这些问题。
首先,我们来谈谈---问题2(该问题是一个数值问题(比较简单),其他的是逻辑问题)
因为 int 类型的范围是(-2^31 ~ 2^31-1),当left、right较大时,(left+right)就有可能大于 int 的范围,产生溢出,所以严谨的做法是 mid = left + ( right - left ) / 2
注意:这里是严谨不严谨的问题,其实它和mid=(left+right)/2在数学上是一样的
而剩下的几个问题其实是同一个体系的逻辑问题,主要关于我们对于二分查找的有效区间的定义
注意:对有效区间的定义非常重要!!!二分查找的 "有效区间" 能帮助我们更好的理解二分算法。其实有效区间就是我们在进行二分查找时不断改变大小的区间,它的范围是在不断的被减半的。
假设:我们定义的有效区间是[left,right)
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int target = 8;
//有效区间[left,right)
//初始化时要注意有效区间
int left = 0;//left在区间之内,所以初始化为0
int right = sz;//right在区间之外,所以初始化为sz
while (left < right)//循环内的区间得符合有效区间的定义,而left与right很明显无法相等(一个闭区间,一个开区间)
{
int mid = left + (right - left) / 2;//严谨的做法
//那这里为什么不用mid=left+(right-left+1)/2?
//其实上面两个mid的求法还是与有效区间的定义有关
//当left与right相差大于1时,mid会一直在有效区间之内
//而当left与right相差为1时,
//mid=left+(right-left)/2,其实就是mid=left,而left在有效区间内,所以可以
//mid=left+(right-left+1)/2,其实就是mid=right,而right不在我们定义的有效区间内,
//或者说right已经被判定过,这样会造成死循环的一直检查right,所以不可以
if (arr[mid] < target)
left = mid + 1;
//这里也要注意有效区间的定义,因为left是闭区间,left=mid,会让mid出现在有效区间之内,
//而mid已经被检查过不符合定义,故left=mid+1
else if (arr[mid] > target)
right = mid;
//这里也要注意有效区间的定义,因为right是开区间,right=mid-1,会让mid-1被排除在有效区间之外,
//而mid-1并没有并检查过,会有遗漏,故right=mid
else
{
printf("找到了\n");
break;
}
}
if (left > right)
printf("没找到\n");
return 0;
}
在看过上面代码的解释后,我们先完成下面要求的二分查找,在比对答案和解析,看是否理解
假设:我们定义有效区间是(left,right]
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int target = 8;
//有效区间(left,right]
//初始化时要注意有效区间
int left = -1;//left在区间之外,所以初始化为-1
int right = sz - 1;//right在区间之内,所以初始化为sz-1
while (left < right)//循环内的区间得符合有效区间的定义,而left与right很明显无法相等(一个闭区间,一个开区间)
{
int mid = left + (right - left + 1) / 2;//严谨的做法
//那这里为什么不用mid=left+(right-left)/2?
//其实上面两个mid的求法还是与有效区间的定义有关
//当left与right相差大于1时,mid会一直在有效区间之内
//而当left与right相差为1时,
//mid=left+(right-left)/2,其实就是mid=left,而left在有效区间外,
//或者说left已经被判定过,这样会造成死循环的一直检查left,所以不可以
//mid=left+(right-left+1)/2,其实就是mid=right,而right在有效区间内,所以可以
if (arr[mid] < target)
left = mid;
//这里也要注意有效区间的定义,因为left是开区间,left=mid+1,会让mid+1被排除在有效区间之外,
//而mid+1并没有并检查过,会有遗漏,故left=mid
else if (arr[mid] > target)
right = mid - 1;
//这里也要注意有效区间的定义,因为right是闭区间,right=mid,会让mid出现在有效区间之内,
//而mid已经被检查过不符合定义,故right=mid-1
else
{
printf("找到了\n");
break;
}
}
if (left > right)
printf("没找到\n");
return 0;
}
假设:我们定义的区间是[left,right]呢?
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int target = 8;
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = left + (right - left + 1) / 2;
//int mid=left+(right-left)/2
//这里两种写法都可以,因为mid无论等于left还是right都在有效区间内
if (arr[mid] < target)
left = mid + 1;
else if (arr[mid] > target)
right = mid - 1;
else
{
printf("找到了\n");
break;
}
}
if (left > right)
printf("没找到\n");
return 0;
}
总结一下:其实left、right、mid如何变化,最终都是要看我们如何定义的有效区间,建议我们在写二分查找的时候把有效区间写成注释,方便我们分析和写代码
附:二分的基础题( 模板题 )
题目:要求找小于等于target的最大值,所给数组有序
限制:时间复杂度是O(log n)
其实一看时间复杂度我们就第一反应想到二分查找,如何写代码呢?
假设有数组{1,2,3,4,5,6,8,9,10},target = 7
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int target = 7;
int left = 0;//这个依旧是看有效区间
int right = sz - 1;//这个依旧是看有效区间
int ans = -1;
while (left <= right) //这里选择的有效区间为[left,right],可以选择其他的区间试着写写
{
int mid = left + (right - left + 1) / 2;//这个依旧是看有效区间
//如果满足条件,先记录答案,在调整left,因为后面有可能出现比mid大,且也满足条件的答案
if (arr[mid] <= target)
{
left = mid + 1;
ans = mid;
}
else//如果不满足条件,则>mid的数字都不满足条件,直接跳过
right = mid - 1;
}
if (ans < 0)
printf("target是最小值");
else
printf("%d",arr[ans]);
return 0;
}