“清明时节雨纷纷,路上行人梳中分。”
1.基本概念
今天我给大家带来一个清朝老算法“中分”(二分)算法,二分算法还很讲究,分为二分查找和二分答案,今天我就分别给大家细说一下这两个算法。
二分查找是一种在有序数组中查找特定元素的搜索算法,也被称为二分搜索、折半搜索或对数搜索。这种算法通过不断地将搜索范围缩小一半来找到目标元素。其基本原理是从数组的中间元素开始比较,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中继续查找,同样从中间元素开始比较。这个过程会持续进行,直到找到目标元素或搜索范围为空,表示找不到目标元素。
二分搜索算法的时间复杂度在最坏情况下是O(log n),其中n是数组的元素数量。这是因为每次比较都会将搜索范围缩小一半,因此算法的运行时间与输入规模的对数成正比。这种算法效率非常高,特别适用于大规模数据的搜索问题。
二分搜索算法的应用不仅限于精确匹配查找,还可以扩展到计算一个值的排名(比它更小的元素的数量)、前趋(下一个最小元素)、后继(下一个最大元素)以及最近邻等。通过两个排名查询(又称秩查询)可以借由二分搜索算法来运行搜索两个值之间的元素数目的范围查询。
二分查找就是不断取中间值,如果查找的那个东西符合条件,并且比中间值大,就让中间值+1,如果比中间值小,就让中间值-1。
2.实现思想
二分答案是一种在答案可能的范围内进行二分查找的方法,直到找到最优答案。这种方法要求满足条件的答案是单调有序的,其基本思想是在答案可能的范围([L,R])内二分查找答案,不断检查当前答案是否满足题目的要求,根据检查结果更新查找的区间,最终取得最符合题目要求的答案进行输出。二分答案算法与二分查找类似,但侧重点不同:二分查找侧重于查找一个元素是否存在,而二分答案则侧重于找到答案
二分搜索算法的实现基于分治策略,充分利用了元素间的次序关系。它要求数组必须是有序的(单调的),因为只有在有序数组中,才能通过比较中间元素来有效地缩小搜索范围。如果数组无序,则无法保证每次比较都能有效地缩小搜索范围,从而无法保证算法的正确性和效率。
二分答案算法的实现过程为:
- 算法思想:在答案可能的范围([L,R])内进行二分查找,每次检查当前答案是否满足题目的要求。根据检查结果,更新查找的区间。
- 单调性要求:与二分查找要求数列有序不同,二分答案要求满足条件的答案是单调有序的,即答案之间存在明确的单调关系。
- 应用场景:当问题的答案在一个区间内,且这个区间内的答案与某个变量存在单调性时,可以使用二分答案算法。通过不断二分这个区间,直到找到最优答案。
二分答案其实就是对答案进行查找。
我说这些,很多人可定会一脸懵逼。
为什么要用这些算法呢?我举两个实际例子你们就知道了。
1.
一天上数学课的时候,小明问了小张一个问题
小明:“小张,我如果现在给你一段数,你能找出我要求的数是第几个吗?”
小张:“当然了,你说吧。”
小明:“在1,3,5,7,9......(到一百)这段数中,9是第几个呢?”
小张:“从前往后数,当然是第5个了。”
小明:“那如果是97呢?”
小张:“那我会从后往前数。”
小明:“那如果是55呢?”
小张:“那我就一个个数呗,直到数到55。”
小明:“你那样数太麻烦了,是不是有些更简便的方法呢?”
小明说的方法就是二分查找,通过不断查找这段数列的中间值+1或者-1,来达到求到答案的目的。
2.
伐木工人在看一些树,可是为了达到不破坏生态环境的目的,他们砍的树木不能过长以免达不到让树木继续生长的目的,可是他们的老板却急需一批木头,让你帮他们算一下,怎样砍才能不破坏生态环境,还能给老板交付木头?
如图所示,难道要从9开始一个个往下搜吗?这样未免也太麻烦了,像这题数量小还好,如果数据大一点呢?这就要用到我们的二分答案。像这题我们就可以从5开始搜,如果砍的数量比需要的木材还多,就往大了搜,如果砍的数量比需要的木材多,那就往下的搜,直到找到合适的答案。
3.模板展示
这是二分查找的模板
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[50010],l,r,mid,ans,x; //n是数列长度,a数组是数列,l是起点,r是终点,mid是数列中点,ans是最终答案,x是询问的值
signed main()
{
cin>>n>>x;
for(int i=1;i<=n;i++)cin>>a[i];
l=1,r=n;
while(l<=r)
{
mid=(l+r)/2; //取数列中点
if(a[mid]<x)l=mid+1; //如果比x小的话,就证明1~mid都比x小,去mid+1~r找
else if(a[mid]>x)r=mid-1; //如果比x大的话,就证明mid~r都比x大,去l~mid-1找
else //证明找到了
{
ans=mid;
break;
}
}
cout<<ans;
return 0;
}
很多人看了模板以后肯定还是一脸懵逼
没有关系我们来看图解释一下。
有下面这一串数列,我们来求一下2的位置在哪
(注:L是left的缩写,意思是左边,R是right的缩写,意思是右边,M是middle的缩写意思是中间)
一和六的中间是3,但是三不是二,二比三小,所以拿middle-1当右值,所以继续搜
1和2的中间值是1,比2小所以拿1+1当左值继续搜,2和2的中间值为2,搜到了就直接输出就行了
这是二分答案的模板
求大于等于目标的最小值
int ans;
while(l<=r)
{
int mid=l+r/2
if(check(mid)){
ans=mid;
r=mid-1;
}
else l=mid+1;
}
求小于等于目标的最大值
int ans;
while(l<=r)
{
int mid=l+r/2
if(check(mid)){
ans=mid;
l=mid+1;
}
else r=mid-1;
}
两个模板都定义一个ans,来计算,每次查找的答案。
二分答案是对答案进行二分, 所谓答案:在某个题里,我们可以确定答案一定在某个确定范围内,我们可以给出一个判定,来判断这个答案是否合理。其原理和二分查找差不多(作者在这里就不过多说明了,如果不懂原理,请仔细观看上面的解释)
4.练习巩固
P1571 眼红的Medusa一道很基础题
BUT
这题的数据很大,纯暴搜的话肯定是不行的,所以我们这题要做一点优化,那当然就想到了二分,这题给了你一个区间,让你求谁分别获得了两个奖项,所以这题我们用二分查找来做。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,k,a[100005],b[100005],shu,s1,s2,gs,to;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=k;i++)scanf("%d",&b[i]);
sort(b+1,b+1+k);
for(int i=1;i<=n;i++)
{
int low=1,high=k;
while(low<=high)
{
int mid=(low+high)/2;
if(b[mid]==a[i])
{
cout<<a[i]<<" ";
break;
}
else if(b[mid]<a[i])low=mid+1;
else high=mid-1;
}
}
return 0;
}
这题给了你一些树,问你怎样切才能使切的木材数量为想要的数量,并且每段木材长度最大。
这题如果纯暴力做的话也行,但是一定那不来了满分。
所以我们这题就要像一个更简单的思路,那就是二分,拿二分答案去做,在一个区间内不断找,看怎样才能找出小于等于目标的最大值。
代码:
#include<bits/stdc++.h>
using namespace std;
long long n, k,ans,x;
long long a[100005];
long long cmp;
bool f(long long x) {
long long ans = 0;
for (int i = 1; i <= n; i++) {
ans += a[i] / x;
}
return ans >= k;
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for(int i=1;i<=n;i++){
cmp+=a[i];
}
if(cmp<k){
cout<<0;
return 0;
}
long long l = 0, r = 100000000;
long long mid;
while (l <= r) {
mid = (l + r) / 2;
if (f(mid)) {
l = mid + 1;
ans=mid;
} else {
r = mid - 1;
}
}
cout << ans << endl;
return 0;
}
最后我要说一点,恩师金局里面提到过,二分它,思路很好想,细节是魔鬼。写二分题的时候一定要注意细节。
作者对二分的讲解就这么多了,感谢大家的观看,如果有疑问的,或者有更好见解的,欢迎留言了,谢谢。
最后
作者想要一个赞和关注。
求求了,给一个赞和关注吧,阿里嘎多。