引言
在C++中,二分算法是一种我们再熟悉不过的算法了。甚至在猜数游戏中也有二分思维的运用。这篇文章我们就来讲一讲二分算法。
目录
二分查找思路
在平时玩猜数游戏时,我们一般不会一个一个猜(这属于枚举算法的应用),而是会“对半砍”。比如一个数在0~1000之间,我们会先猜500(1000/2),若猜小了则猜250(500-500/2),若猜大了则猜750(500+500/2);以此类推,每次都猜上一个要猜的数的一半,再根据猜大或猜小继续调整,直至猜对。
没错,这运用了二分查找思路。二分查找是一种在有序数组中查找特定元素的算法。它通过将目标值与数组的中间元素进行比较,从而确定目标值可能存在的位置。二分查找大大缩减了时间复杂度,有利于应对数据较大的情况。例如一个程序的数据有5*10^8,使用for循环会超时,因此可以选用二分查找算法。
二分查找图解
通过这张图可以发现,二分查找算法只用于查找一个有序的数列。若该数列无序,例如1 3 7 2,就无法使用二分查找算法。
二分查找代码
对于二分查找算法,我们需要用到三个变量:mid,left和right。
- middle(常写作mid或m):left和right的中间值,我们在判断时的核心就是使用middle变量。其公式为:(left+right)/2或(left+right)>>1。
- left(常写作l):为二分查找的左边界,一般初始化为1。每次进行二分查找后,若middle小于需要查找的数,就收缩左边界至middle+1(需要+1的原因是middle的值已经被查找过了,若不+1,可能会算法错误)。
- right(常写作r):为二分查找算法的右边界,初始化为n(元素数量)。每次进行二分查找后,若middle大于需要查找的数,就收缩左边界至middle-1。(与left相反)
同时,我们还需要一个while循环,判断左边界是否小于等于右边界。若左边界大于右边界时,这说明查找结束,若还未找到,说明需要查找的值不在数列当中。
以下为示例代码:
int l = 1,r = n;
while(l <= r){
int mid = (l + r) / 2;
if(a[mid] == x){ //a[mid]的值就是需要查找的数
cout << mid;
return 0;
}else if(a[mid] < x){ //a[mid]的值小于需要查找的数
l = mid + 1;
}else{ //否则(a[mid]的值大于需要查找的数)
r = mid - 1;
}
}
二分答案思路
二分答案思路是一种解决某些优化问题的常用算法思路。它通过确定问题的答案范围,并使用二分查找的方法在该范围内逼近最优解。二分答案思路常用于解决优化问题,如最大化或最小化某个函数的值、找到满足特定条件的最优解等。通过将问题转化为判断答案是否满足某个条件,然后利用二分查找的方法逼近最优解,可以在较短的时间内得到较好的解答。
首先需要确定问题的答案范围。如果问题的答案是一个连续的实数值,可以确定一个最小值 left 和一个最大值 right,作为答案的上下界。如果问题的答案是一个整数值,可以确定一个最小值 left 和一个最大值 right,作为答案的上下界。
再利用二分查找的方法在答案范围内逼近最优解。在每一次迭代时,计算中间值mid=(left + right)/2(像二分查找那样),然后根据 mid 的取值与问题的性质,判断目标值可能在左半部分还是右半部分。
然后根据 mid 的取值更新答案范围。如果 mid 的取值满足问题的要求,表示答案可能在 mid 的右半部分。此时,令 left = mid 或 left = mid + 1,然后回到步骤 2。如果 mid 的取值不满足问题的要求,表示答案可能在 mid 的左半部分。此时,令 right = mid 或 right = mid - 1,然后回到步骤 2。
重复以上步骤,直到 left 大于 right(理由同上)。最终,得到的 left 或 right 即为问题的最优解。
二分答案代码
上面已经分析过思路了,废话不多说,直接上代码:
//主函数内
while(l <= r){
int mid = left + (right - left) / 2; //计算中间值
if(check(mid)){
left = mid + 1; //满足条件,更新答案范围为[mid+1,right]
}else{
right = mid - 1; //不满足条件,更新答案范围为[left,mid-1]
}
}
//check函数内容需要根据问题自行编写
例题:木材加工
思路 首先我们输入 n 和 k 并且运用二分找到合适的尺寸,而l 必须要足够小,r 必须要足够的大。题中写道数组中的数最大不会超过 100000000 ,所以我们设 100000001 就可以了。现在就走到了判断的环节,我们如何判断 mid 是太小还是太大呢?我们需要编写一个新函数 -- f。在函数 f 中,我们依次要判断 a 中的每一个数并计算出能切出多少个 mid ,还要用一个变量 ans 储存他们,如果 ans 分的分数比 k 多或者正好等于,返回真。如果是小于 k ,返回假。当 f 返回的是真的时候,我们就要试试还能不能把 mid 调大一点,就要l=mid+1;如果返回的是假,我们就加的太大了,就要把 mid 调小一点,就要r=mid-1。一直到结束,输出 l 就可以了。(鸣谢:MilkyCoffee)
题解
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int n,k;
bool check(int mid){
int sum = 0;
for(int i = 1;i <= n;i++){
sum += a[i] / mid;
}
return sum >= k;
}
int main(){
cin >> n >> k;
for(int i = 1;i <= n;i++) cin >> a[i];
int l = 1,r = 1e8;
int ans = 0;
while(l <= r){
int mid = l + r >> 1;
if(check(mid) == true){
ans = mid;
l = mid + 1;
}else{
r = mid - 1;
}
}
cout << ans;
return 0;
}