二分查找算法:
二分查找算法是具有二段性
我们在平常的二分中,通常会认为要有序的才能进行二分,但事实是无序的也能进行二分,只要在一道题中能够将其分成两个不同的性质,就能对其循环二段性,一步一步二分。
二分也并不意味着一定要将其对等半分,还可以对其进行,三分,四分,五分……等等
接下来会有题目进行讲解
二分的主要类型:
- 朴素的二分模板(较为局限)
- 查找左边界的二分模板
- 查找有边界的二分模板
朴素的二分模板:
朴素的二分模板就是简单的二分,给个二分值mid,判断是否大于left,是否小于right,是否等于target(目标值即可)
如图所示
- 当mid < target 时,left = mid + 1 再次进入循环判断
- 当mid > target 时,right = mid - 1 再次进入循环判断
- 当mid = target 时,return
//模板为
while ( left <= right ){
int mid = left + ( right - left ) / 2;
//当然也可以 (left+right)/2,上面这样做是为了当数字特别大时防止溢出;
if(……)
left = mid + 1;
else if(……)
right = mid +1;
else
return ……;
}
https://leetcode.cn/problems/binary-search/submissions/497655071/
上面这道就是朴素二分模板地具体应用
int search(int* nums, int numsSize, int target) {
int l=0,r=numsSize-1;
while(l<=r){
int mid = (l+r)/2;
//待会展示除以不同的数值的时候,是否也能二分
if(nums[mid]<target)
l=mid+1;
else if(nums[mid]>target){
r=mid-1;
}
else
return mid;
}
return -1;
}
分析问题,按照模板,先left 再right 再target;
回到先前二分,是否只是对等分?答案不是的
显而易见,二分并不一定要除以2,即不一定要对等分,所以,二分的本质就是二段性,只要能分成性质不同的两段即可;
寻找左端点的二分模板:
引入一道例题https://ww
#include<stdio.h>
int main(){
const int N=100010;
int n,m;
int q[N];
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
while(m--){
int x;
scanf("%d",&x);
int l=0,r=n-1;
//找左边界
while(l<r){
int mid=l+(r-l)/2;
if(q[mid]<x)
l=mid+1;
else
r=mid;
}
if(q[l]!=x)
printf("-1 -1\n");
else{
printf("%d ",l);
l=0;
r=n-1;
//找右边界
while(l<r){
int mid = (l+r+1)/2;
if(q[mid]<=x){
l=mid;
}
else{
r=mid-1;
}
}
printf("%d\n",l);
}
}
}
数的范围:大致提议就是在一个数组中找出一个指定元素的起始坐标和终点坐标
我们便会有一个数组,eg: [1,2,3,3,3,4,5] ,我们需要返回3的起始和终点的坐标值
很容易知道当我们用朴素二分查找的时候,只能找到一个值,并不能返回两个值,因此我们便知道朴素二分已经不适用于此题的解
发现二段性,就可以用二分!!!
如图所示,我们可以再来回顾一下,二分是具有强烈的二段性,很容易知道可以把此题的性质分为<3和>=3两段性质,我们现要求的就是区间>=3时的第一个3
分两种情况
- 当mid的值小于3时,注意只是小于,没有小于等于
所以当x<t 时,将left = mid +1 ;让其继续缩小区间,在进行新的判断
我们不能跟朴素二分一样寻找当x = t 时的情况,因为当 x = t 时,并不一定时左端点,并不一定会进入第二次循环,要记住,我们要找的时左端点。(区间的左端点就是第一个3的坐标位置)
所以,我们在求左端点的情况下只有两种情况,第二种就是当 x >= t 时
- 当 mid的值大于等于 3时
更新方式就是 right = mid ;
总结就是
- 当 x < t 时,更新left = mid +1 -> 新的循环
- 当 x >= t 时,更新 right = mid -> 新的循环
细节处理
-
循环条件
只能时 left < right , 不能跟朴素二分一样有 相等条件成立
因为当left = right 的时候,有结果的话,已经是相等的了,也会因此进入死循环,无论时left走还是right走,最后都相等,不断进入循环,环啊环
-
求中点操作
我们在求朴素的时候,用不用+1,其实都无所谓,但在求左端点,就很有所谓了
举个例子
一个数组,只有 0,1两个元素,target = 1 ;
当使用的是:int mid = ( left + right + 1 ) / 2; 时
可以看出此刻的mid =1,是在right的位置上,
若命中第一个条件(mid < t)时,left = mid +1;则left会跳到right的后面去,从而会导致循环结束
若命中第二个条件(mid>=t )时,right = mid; 则right会一直在原位,且会不断进入循环,因为left与right的关系没有发生改变
所以不能+1,只能 int mid =(left + right) /2;
所以模板为:
while ( left <right){
int mid = ( left + right ) /2;
if(……){
right=mid;
else{
left = mid + 1;
}
寻找右端点的二分模板:
就显而易见的能力就是能用来找最右边的3
x是mid 的值
一样的,根据性质和二段性将他分成两个部分,一个是x<=3, 另一段时x>3
分两种情况
当mid的值小于等于3时
当mid的值小于等于3时
总结就是·:
- 当x<=3时,left = mid ->进入新的循环
- 当x>3时,right = mid -1 ->进入新的循环
细节处理
循环条件
与寻找左边界原理一致,当left = right 时会出现不同的死循环,所以只能left < right;
中点处理
int mid = (left + right +1)/2;
在此处便要+1了
所以模板为
while(left < right ){
int mid = (left+right+1)/2;
if(……)
left = mid;
else
right = mid -1 ;
}
所以:数的范围那题的解答是
#include<stdio.h>
int main(){
const int N=100010;
int n,m;
int q[N];
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
while(m--){
int x;
scanf("%d",&x);
int l=0,r=n-1;
//找左边界
while(l<r){
int mid=l+(r-l)/2;
if(q[mid]<x)
l=mid+1;
else
r=mid;
}
if(q[l]!=x)
printf("-1 -1\n");
else{
printf("%d ",l);
l=0;
r=n-1;
//找右边界
while(l<r){
int mid = (l+r+1)/2;
if(q[mid]<=x){
l=mid;
}
else{
r=mid-1;
}
}
printf("%d\n",l);
}
}
}
以上就是整数二分查找算法的运用了