二分查找:
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。值得注意的是折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
让我们回忆一下平时我们如何在一本词典里查找一个单词呢?
查找单词过程的原理与二分查找的思路几乎是相同的。
我们在字典(单词按照“字典序”进行排序的)中查找单词,先假设字典页数一共200页。第一步都是翻到字典的中间页100页,然后再判断是该单词是否出现在该页,又或者出现在字典的前面还是后面。若是前面则翻到字典的50页,若是后面则翻到字典的150页,查找范围从200页——100页——50页…
查找的范围不断缩小,这也就是二分查找效率的体现!
实现二分法查找基本思路
(1)首先,从数组的中间元素开始搜索,如果该元素正好是符合条件的目标元素,则搜索过程结束,否则执行下一步。
(2)如果目标元素大于/小于中间元素,则在数组大于/小于中间元素的那一半区域查找,然后重复步骤(1)的操作。
(3)如果某一步数组为空,则表示找不到目标元素。
二分法查找的时间复杂度O(logn)。
参考博客
代码实现:在长度为 len 的 arr 数组中二分查找元素 ans 。
int l=0, r=len-1
while(l<r){
int mid = (r+l)/2;
if(arr[mid]>ans){
r=mid-1;
}
else if(arr[mid]<ans){
l=mid+1;
}
else if(arr[mid]==ans){ //找到
ans=mid;
break;
}
}
例题一:
输入 n(n≤10 6) 个不超过 109的单调不减的(就是后面的数字不小于前面的数字)非负整数 a1 ,a2 ,…, an,然后进行m(m≤10 5 ) 次询问。对于每次询问,给出一个整数q(q≤10 9),要求输出这个数字在序列中的编号,如果没有找到的话输出 -1 。
输入格式
第一行 2 个整数 n 和 m,表示数字个数和询问次数。
第二行 n 个整数,表示这些待查询的数字。
第三行 m 个整数,表示询问这些数字的编号,从 1 开始编号。
输出格式
m 个整数表示答案。
输入输出样例
输入
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
输出
1 2 -1
该题为模板题,注意要处理找不到的情况即可。
#include<bits/stdc++.h>
using namespace std;
int data[1000000+5], q[100000+5];
int main()
{
int n, m;
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1; i<=n; i++){
cin>>data[i];
}
for(int i=1; i<=m; i++){
cin>>q[i];
}
for(int i=1; i<=m; i++){
int l=1, r=n;
int num=q[i];
while(l<r){
int mid = (l+r)/2;
if(data[mid]<num){
l = mid+1;
}
else{
r = mid;
}
}
if(data[r]==num){
cout<<r<<" ";
}
else{
cout<<-1<<" ";
}
}
return 0;
}
例题二
米尔科的伐木机工作过程如下:米尔科设置一个高度参数H(米),伐木机升起一个巨大的锯片到高度H,并锯掉所有的树比H高的部分(当然,树木不高于H米的部分保持不变)。米尔科就行到树木被锯下的部分。
例如,如果一行树的高度分别为20,15,10和17,米尔科把锯片升到15米的高度,切割后树木剩下的高度将是15,15,10和15,而米尔科将从第1棵树得到5米,从第4棵树得到2米,共得到7米木材。
米尔科非常关注生态保护,所以他不会砍掉过多的木材。这正是他为什么尽可能高地设定伐木机锯片的原因。帮助米尔科找到伐木机锯片的最大的整数高度H,使得他能得到木材至少为M米。换句话说,如果再升高1米,则他将得不到M米木材。
输入格式
第1行:2个整数N和M,N表示树木的数量(1<=N<=1000000),M表示需要的木材总长度(1<=M<=2000000000)
第2行:N个整数表示每棵树的高度,值均不超过1000000000。所有木材长度之和大于M,因此必有解。
输出格式
第1行:1个整数,表示砍树的最高高度。
输入输出样例
输入
5 20
4 42 40 26 46
输出
36
思路:
①、求出每棵从低到高的树的前缀和,方便计算可得木材
eg:sum[N]:第N棵树的高度前缀和
前缀和概念
②、初始化:H_l = 0,H_r = 树的最高高度。
③、利用 H_l、H_r 二分确定一个高度 H_mid。
④、初始化:s_l = 0,s_r = 树的数量。
⑤、利用 s_l、s_r 二分确定一个高度小于等于H的树 s_mid。
⑥、可计算出在H_mid高度下得到的木材 m
m = sum[N] - sum[r_s] - (N-r_s) * H
⑦、判断M与m的大小关系。
若M<m,则 H_l = H_mid,重复③~⑥
若M>m,则 H_r = H_mid - 1,重复③~⑥
若M=m,则退出循环,输出答案。
⑧、若m始终不能与M相等,则最终 H_l 会与 H_r 相交,输出 H_r 即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll data[1000000+7], sum[1000000+7];
int main()
{
ll N, M;
ios::sync_with_stdio(false);
cin>>N>>M;
for(ll i=1; i<=N; i++){
cin>>data[i];
}
memset(sum, 0, sizeof(sum));
sort(data+1, data+N+1);
for(ll i=1; i<=N; i++){
sum[i] = sum[i-1]+data[i];
}
//选择要砍的树的最高高度
int l_h=0, r_h=data[N];
while(l_h<r_h)
{
int H=(l_h+r_h+1)/2; //"+1"防止陷入循环
//找出小于或等于最高高度的树
int l_s=0, r_s=N;
while(l_s<r_s)
{
int S=(l_s+r_s+1)/2;//"+1"防止陷入循环
if(data[S]>H){
r_s=S-1;
}
else
l_s=S;
}
if(sum[N]-sum[r_s]-(N-r_s)*H<M){
r_h=H-1;
}
else {
l_h=H;
}
}
cout<<r_h;
return 0;
}
以后如果遇到较为经典的二分查找类型的题会陆续添上……
希望将自己的学习经验分享给有需要的人。
我是小郑,一个坚持不懈的小白