14.二分查找和二分答案

二分查找

建立的前提是一个有序的序列

1.划定范围

2.找中间位置

value1020455010012012130500600
index012345678910

假设我们要去查找50

查找过程:

L = 1

R = 10

MID = (L+R)>>1

由于MID值 大于 目标50,所以

R = MID-1

L 还是呢个L

MID = (L+R)>>1

由于MID 值小于了目标50,所以要把L往右边移动

L = MID + 1

R还是呢个R

MID = (L+R)>>1

此时我们会发现 L 的值为3 R值为4 MID 值为3

由于MID 对应的值小于了目标50,所以要把L往右边移动

再次移动后我们会发现什么?????

L 在 4这个位置

R 在 4这个位置

MID 必然也会在4这个位置,因为MID 是由L\R算出来的

现在假设我们要找55,找55的话L再次移动会怎么样,思考一下

讲一下对数的概念:

二分查找的时间复杂度为

$$
log2n
$$

二分查找常见问题

1. 二分查找元素x在数列中是否存在
1. 二分查找左边界:第一次出现的位置(>=该元素的第一个数的位置)
1. 二分查找右边界:最后一次出现的位置(>该元素的第一个元素的位置)

P1272 二分查找模板

#include<bits/stdc++.h>
using namespace std;
const int N = 1000100;//数组范围常量
int a[N];
int x,n;
int main() {
//cin>>n; 数据量大的题目我们尽量用scanf
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
scanf("%d",&x);

//二分循环
//定义边界
int l = 1,r = n,mid;

while(l <= r){
mid = (l+r)>>1;
if(x < a[mid]){
r = mid - 1;
}else if(x > a[mid]){
l = mid + 1;
}else if(x == a[mid]){
cout<<mid;
return 0;//我也可以写break
//但是break会导致我后面再用标记变量去处理
}
}
cout<<-1;
}

注意:

求 mid 的3种写法 (1)mid=(l+r)/2

(2)mid=(l+r)>>1(括号可以不写)

(3)mid=l+(r-l)/2(可以防止 |+r 越界的情况 ,数学原理上跟第一个一样,但是没有先加起来)

左闭右开的写法

有些人或者老师喜欢教左闭右开,我这里教的是闭区间

左闭右开的优势:1.算数好算 2.连续的区间表达

[1,2] 实际上是两个数

[1,3)直接3-1就好了

考虑方面 闭区间 更好考虑

科学方面 左闭右开 更科学

#include<bits/stdc++.h>
using namespace std;
const int N = 1000100;//数组范围常量
int a[N];
int x,n;
int main() {
//cin>>n; 数据量大的题目我们尽量用scanf
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
scanf("%d",&x);

//二分循环
//定义边界
int l = 0,r = n+1,mid;

while(l < r){
mid = (l+r)>>1;
if(x < a[mid]){
r = mid;
}else if(x > a[mid]){
l = mid + 1;
}else if(x == a[mid]){
cout<<mid;
return 0;//我也可以写break
//但是break会导致我后面再用标记变量去处理
}
}
cout<<-1;
}

二分查找左边界 L > R 停止查找

P1819 二分查找左边界(第一次出现)

value10103030303030304040
index012345678910

模拟一下分别查找 30、10、40、15、50

我们会发现第一个问题,如果直接模拟,就会导致mid就是我们要找的目标

所以通过模拟得知 L > R 停止查找

#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
int a[N];
int n, q; //n个数字 q次询问
//目标:
//求元素x在数组中首次出现的位置
int fun(int x) {
    //确定好边界
    int l = 1, r = n, mid;
    while (l <= r) {
        mid = (l + r)/2;
        if (x < a[mid]) r = mid - 1;
        else if (x > a[mid]) l = mid + 1;
        else if (x == a[mid]) r = mid - 1;
    }
    if (a[l] == x) return l;
    else return -1;
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    cin >> q;
    int x;
    for (int i = 1; i <= q; i++) {
        cin >> x;
        cout << fun(x) << " ";
    }
}

P1820 二分查找右边界(最后一次出现)

value10103030303030304040
index012345678910

找30

自己来模拟一下

留下评论

您的邮箱地址不会被公开。 必填项已用 * 标注