二分查找(附例題)

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

以後如果遇到較爲經典的二分查找類型的題會陸續添上……
希望將自己的學習經驗分享給有需要的人。
我是小鄭,一個堅持不懈的小白

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