二分查找是一种非常高效但却十分使用的算法,可以在O(logN)下快速找到自己想要的元素。
先来看看基础模板题:
https://www.luogu.com.cn/problem/P2249https://www.luogu.com.cn/problem/P2249
题目描述
输入 𝑛 个不超过 109 的单调不减的(就是后面的数字不小于前面的数字)非负整数 𝑎1,𝑎2,…,𝑎𝑛,然后进行 𝑚 次询问。对于每次询问,给出一个整数 𝑞,要求输出这个数字在序列中第一次出现的编号,如果没有找到的话输出 −1 。
输入格式
第一行 2 个整数 𝑛 和 𝑚,表示数字个数和询问次数。
第二行 𝑛 个整数,表示这些待查询的数字。
第三行 𝑚 个整数,表示询问这些数字的编号,从 1 开始编号。
输出格式
输出一行,𝑚 个整数,以空格隔开,表示答案。
输入输出样例
输入 #1
11 3 1 3 3 3 5 7 9 11 13 15 15 1 3 6
输出 #1
1 2 -1
先抛开这个例题(这个例题相对比较有变化),我们一直会有一个误区,二分只能用来查询有规律的数组元素,那就大错特错了,二分归根到底是一种通过查询前后差异的算法,可以清楚找到一个断点,断点之前的元素满足规则P,而之后的元素满足规则Q,适用面是很广的。先从最简单的做起。
假如有这样一个数组,1,2,4,5,7,8,9,10.我们要找到7的位置,我们会怎么做呢?遍历一遍在这里自然是可以的,但一旦数组元素数量有很多呢,然后又要多次查询怎么办?我们可以二分去做,顾名思义,二分查找就是从中间位置开始每一次尽可能大地去缩小范围,就像数字炸弹,1-100,我们会先才50,那么范围会瞬间少了一半。以此类推,直到找到想要的答案。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int n, t;
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++)
cin >> a[i];
cin >> t;
int l = 1, r = n, mid;
while(l <= r)
{
mid = l + r >> 1;
if(a[mid] == t)
break;
else if(a[mid] > t)
r = mid - 1;
else
l = mid + 1;
}
if(l > r)
cout << -1 << endl;
else
cout << mid << endl;
}
这就是最基础的二分查找了。
回到例题,要求我们找到第一次出现的位置,也就是说,我们找到符合数后依然不能停止,要继续向前。由此我们可以修改一下。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int a[N];
int n, q, t;
int main()
{
scanf("%d%d",&n,&q);
for(int i = 1; i <= n; i ++)
scanf("%d",&a[i]);
while(q --)
{
scanf("%d",&t);
int l = 0, r = n + 1, mid;
while(l + 1 < r)
{
mid = l + r >> 1;
if(a[mid] >= t)
r = mid;
else
l = mid;
}
if(a[r] == t)
printf("%d ",r);
else
printf("-1 ");
}
}
这里用的是最好的板子,二分的模板真的太多了,对其复杂的边界问题,通常我们用祖祖辈辈传下来的板子,禁得起数据的考验。
废话:今天跑了1000m真,脆皮大学生是我没准了。