二分查找应该算是我最早接触的一个算法了,那时候我看的c语言资料里的循环就是拿二分举例的。同时二分的思想也非常好理解,但我没想到的是,这个算法看似非常简单,但是光是想要逻辑清晰的用二分找出一个个限定范围的数确实这么困难(指因为调二分的边界问题调到心态爆炸orz)。
在系统的思考和查阅资料后我现在才有把握,快速且不会因为思考边界问题而出错的手写出下列问题。
这四个问题看起来非常相近,但答案完全不一样
虽然算法很简单,但是我自己写的时候调边界问题调到头秃。
直到查到了这个视频才让我恍然大悟,对二分和边界问题有了更深的理解
【二分查找为什么总是写错?-哔哩哔哩】 https://b23.tv/KBbRkAO
在这个视频内,二分查找的待定序列被划分为两个部分,分别为
①从n点开始向一个方向全满足条件的部分
②从k点开始向一个方向全不满足条件的部分
比如第一个问题,第一个>=5的元素,将序列划分为红 >=5,蓝 <5的两部分
我们现在的目标就是找到红蓝的边界处,如果能找到边界处,那么第一个>=5的元素不就是边界处的右边第一个元素,最后一个<5的元素不就是右边最后一个元素吗......
我之前练习的二分模板都是l+1或r-1,l==r的时候退出查找,这个模板的问题也就是出现频繁边界问题的原因,什么时候l+1?什么时候r-1?循环结束时l和r分别在哪?此时mid处于什么位置?mid会不会越界?
视频给我们提供了一个模板,我们只需要定位到这个红蓝边界就行了,具体返回什么值,由我们自己决定,这样整个二分查找的逻辑就非常清晰了,我们每次二分查找都会找到整个红蓝边界(l为蓝最后一个元素,r为红起始元素。
如果我需要第一个>=5我返回r,最后一个<5我就返回l
这下回到前面的四个问题,设左边的范围为蓝,右边为红
我们先由前面的逻辑,给出定位到边界的模板
int n;
int l=-1,r=n;
void my_binary_search{
while(l+1==r)//l和r分别为两边界的首尾元素所以差1
{
int mid=(l+r)>>1;
if(is_blue(m))//满足左边范围的条件
l=mid;
else//满足右边条件
r=mid;
}
}
此时 l为左范围的最后一个元素,r 为红范围的第一个元素
最后讨论一下细节问题,如果整个序列都不符号某个条件整个序列”都是一个颜色“怎么办?也就是我想找的一个元素,序列中没有。可以推出,这种情况 l==-1或者r==n,在查询的判断一下就行了。
例题:
这个题的思路就是得到最长树的高度,用二分查找在0到最大高度中找出低于这个值的范围内高度h都满足题目条件的边界 ,也就是把高度序列划分为<=n的部分砍完可以满足条件,>n的部分不能两部分,用二分确定边界,然后输出<=n部分的首元素。为但是要注意这题的二分建模h序列是升序的。
#include<bits/std c++.h>
#define ll long long
using namespace std;
ll n, m;
vector<ll>arr(2000000);
int judge(ll h) {
ll ans = 0;
for (int j = 0; j < n; j++) {
if (arr[j] - h > 0)
ans += arr[j] - h;
}
if (ans >= m)
return 1;
return 0;
}
int main()
{
ios::sync_with_stdio(false); cout.tie(NULL);
cin >> n >> m;
ll mx =0x80000000;
for (int j = 0; j < n; j++) {
cin >> arr[j];
mx = max(arr[j], mx);
}
//因为砍的最大高度不会超过最高树的高度
ll l = -1, r = mx+1;
while (l + 1 != r) {
ll mid = (l + r) >> 1;
if (!judge(mid))//如果砍的总高度不够所给需求,降低砍树高度,提高得到木材
r = mid;
else
l = mid;
}
if(l!=0)//虽然这里没什么用,不过养成好习惯判断查询值是否合法
cout << l;
return 0;
}
以下是搜索版本二分查找,供参考
#include<bits/std c++.h>
#define ll long long
using namespace std;
ll n, m;
vector<ll>arr(2000000);
ll mx = -9999999999999;
int judge(ll h) {//相当于判断是否满足查询条件
ll ans = 0;
for (int j = 0; j < n; j++) {
if (arr[j] - h > 0)
ans += arr[j] - h;
}
if (ans >= m)
return 1;
return 0;
}
void dfs(ll l,ll r) {
if (l + 1 == r) {
cout << l;
return;
}
ll mid = (l + r) >> 1;
if(judge(mid))
dfs(mid,r);
else
dfs(l,mid);
}
int main()
{
ios::sync_with_stdio(false); cout.tie(NULL);
cin >> n >> m;
for (int j = 0; j < n; j++) {
cin >> arr[j];
mx = max(arr[j], mx);
}
ll l = -1, r = mx+1;
dfs(-1, mx + 1);
return 0;
}
P2249 【深基13.例1】查找 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
二分模板题
#include <iostream>
#include<ctime>
#include<math.h>
#include<string>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<algorithm>
#define ll long long
using namespace std;
vector<ll>arr(2000000);
ll n, m;
ll l,r;
int judge(ll k,ll t) {//划分序列为左边<t,右边>=t
if (arr[k] >= t)
return 1;
return 0;
}
void myfind(int t) {
//定位l,和r为边界处
while (l + 1 != r) {
ll mid = (l + r) >> 1;
if (judge(mid, t))
r = mid;
else
l = mid;
}
}
int main()
{
ios::sync_with_stdio(false); cout.tie(NULL);
cin >> n >> m;
for (int j = 0; j < n; j++)
cin >> arr[j];
while (m--) {
ll tar;
cin >> tar;
l = -1, r = n;
myfind(tar);
if (arr[r] != tar)//如果序列中不存在tar,会返回其他>=tar的值
cout <<-1<<" ";
else
cout << r+1<<" ";
}
return 0;
}