一.定义
二分算法(Binary Search Algorithm)是一种在有序数组中查找特定元素的算法。它的基本思想是将数组分成两部分,然后确定目标元素可能存在的那一部分,再在该部分中继续进行二分查找,直到找到目标元素或确定目标元素不存在。
以下是二分算法的基本步骤:
- 确定数组的左边界和右边界,通常初始时左边界为0,右边界为数组长度减1。
- 计算中间元素的索引,即左边界和右边界的平均值。
- 比较中间元素与目标元素的大小关系:
- 如果中间元素等于目标元素,则找到目标元素,算法结束。
- 如果中间元素大于目标元素,则目标元素可能在左半部分,将右边界更新为中间元素的前一个索引。
- 如果中间元素小于目标元素,则目标元素可能在右半部分,将左边界更新为中间元素的后一个索引。
- 重复步骤2和步骤3,直到找到目标元素或确定目标元素不存在。
二分算法的时间复杂度为O(log n),其中n是数组的长度。由于每次都将搜索范围减半,因此它比线性搜索算法更高效。然而,二分算法要求数组是有序的,如果数组无序,则需要先进行排序操作。
二.分类
二分算法可以分为两种常见的应用方式:二分查找和二分答案。
-
二分查找(Binary Search):二分查找是在有序数组中查找特定元素的算法,也是最常见的应用方式。它的基本思想是通过比较中间元素与目标元素的大小关系,将搜索范围逐渐缩小,直到找到目标元素或确定目标元素不存在。二分查找的时间复杂度为O(log n)。
-
二分答案(Binary Search for Answer):二分答案是一种在满足某种条件的范围内查找满足特定条件的最优解的算法。它通常用于解决优化问题,例如在一个有序数组中找到满足某种条件的最小或最大值。二分答案的基本思想是通过猜测一个答案,然后根据答案的结果调整搜索范围,逐步逼近最优解。通过不断缩小搜索范围,最终找到满足条件的最优解。二分答案的时间复杂度取决于问题的特性,通常是O(log n)或更高。(笔者认为常见的一般为n logn)
总结来说,二分查找是在有序数组中查找特定元素,而二分答案是在满足某种条件的范围内查找满足特定条件的最优解。它们都是基于二分算法的应用方式,但解决的问题和思路略有不同。
三.详解二分查找
请看这道题:P1678 烦恼的高考志愿 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
(1)首先先分析:
要让每个同学满意度最小,看到这里,我们应该会有想法吧,大该方向确定为贪心或dp。但这类问题比较明显,如果使每个同学的满意度都最小,那整体的满意度就小了。嗯,这里就可以确定为贪心了。
(2)贪心
首先我们可以将数据先排序,然后由同学的成绩去匹配录取成绩,找个刚好大的,求个abs,再找个刚好比他的成绩小的,再求个abs,最后再取个min即可。
(3)二分查找
在贪心中我们不难发现,在找刚好比它大and小的数据时再一个个遍历,那太慢了,明显会超时。既然数据是有限的,我们不妨使用二分查找,这样就从O(n)-->O(log n)。
(4)写出代码
#include<bits/stdc++.h>
#define MIN 0x7fffffff
const int maxn=1e5;
using namespace std;
int a[maxn];
long long ans=0;
int n,m;
// 二分查找函数,返回目标数字与数组中最接近的数字的差的绝对值
int search(int x){
int sum;
int minn=MIN;
int l=0,r=n-1;
while(l<=r){
int mid=(l+r)/2;
sum=abs(a[mid]-x);
minn=min(minn,sum);
if(x>a[mid]) l=mid+1; // 目标数字大于中间元素,更新左边界
else r=mid-1; // 目标数字小于等于中间元素,更新右边界
}
return minn;
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++) cin>>a[i];
sort(a,a+n); // 对数组进行排序,以便进行二分查找
for(int i=0;i<m;i++){
int num;
cin>>num;
ans+=search(num); // 调用二分查找函数,将差的绝对值累加到ans中
}
cout<<ans; // 输出差的绝对值求和的结果
return 0;
}
四.详解二分答案
请看这道题:P2678 [NOIP2015 提高组] 跳石头 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
(1)分析题目
“最短跳跃距离的最大值”。看到这个又可以判定和二分贪心脱不了关系。我先想的是直接排个序,用差分存个数组,直接输出a[m]即可,最后又想了想,不对。如果删一个数据的话,也会对其他数据造成影响。所以还要对差分数组在做处理,这样越想越复杂。那还有更好的办法吗?我们可以想到也可以用二分答案来实现。我们先二分答案枚举答案,再验证可行性即可。
(2)先看代码
#include<bits/stdc++.h>
using namespace std;
int d, n, m;
int a[50005];
// check答案的可行性
bool f(int x){
int now = 0; // 当前位置
int i = 0; // 下个位置
int sum = 0; // 记录需要额外的车的数量
while(i < n + 1){
i++;
if(a[i] - a[now] < x){ // 如果当前位置与上一个位置之间的间距小于x
sum++; // 删掉一个石头,当now不变,i++,也就相当于删掉了i的这块石头
}else{
now = i; // 更新当前位置
}
}
if(sum > m) return false; // 额外的车的数量超过了m,无法满足条件
else return true; // 额外的石头的数量不超过m,满足条件
}
int main(){
cin >> d >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
a[n + 1] = d; // 将终点位置加入数组
int l = 1, r = d; // 二分查找的左右边界
int ans = 0; // 最终答案
while(l <= r){
int mid = (l + r) / 2;
if(f(mid)){ // 如果满足条件
ans = mid; // 更新答案
l = mid + 1; // 向右边搜索
}else{
r = mid - 1; // 向左边搜索
}
}
cout << ans; // 输出答案
return 0;
}
(3)你可能有的疑惑
1.当sum<m的时候也返回,会不会出错,答案不应该是sum==m吗?
答:记住这是二分答案,一定会找出最优和正确的答案!