二分查找(附例题)

二分查找:
二分查找也称折半查找(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;
}

以后如果遇到较为经典的二分查找类型的题会陆续添上……
希望将自己的学习经验分享给有需要的人。
我是小郑,一个坚持不懈的小白

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章