【C++算法】二分算法

引言

在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;
}

  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值