当前位置:   article > 正文

C++二分算法(二分查找&二分答案)细节详解_c++二分答案

c++二分答案

 二分算法可以分为二分查找二分答案

以在一个升序数组中查找一个数为例。它每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找;如果中间元素大于所查找的值同理,只需到左侧查找。

首先明确二分查找与二分答案有何区别?

二分查找:在一个已知的有序数据集上进行二分地查找
二分答案:答案有一个区间,在这个区间中二分,直到找到最优答案

二分查找

当我们要从一个序列中查找一个元素的时候,最快想到的方法就是暴力查找法(即:从前到后依次查找)。但这种方法过于无脑,适用于元素较少的时候,一旦元素个数多起来,效率是非常低下,所以在实际中这种查找的方法是被摒弃的。

这里就不得不介绍一种简单且效率较高的查找方法了:二分查找法,又称折半查找法。但该方法是建立在有序的前提下的。

二分查找法前提条件是:查找的序列必须是有序的。即该序列中的所有元素都是按照升序或者降序排列好的,元素与元素只间的差值虽然是随机的,但始终是在递增或递减。

模板一(基本的二分查找)

这个场景是最简单的,肯能也是大家最熟悉的,即搜索一个数,如果存在,返回其索引,否则返回 -1

  1. int check(int num[],int size,int t){
  2. int left=0,right=size-1; // 定义了t在左闭右闭的区间内,[left, right]
  3. while(left<=right){ //当left==right时,区间[left, right]仍然有效
  4. int mid=left+((right-left)/2);//等同于(left+right)/2,防止溢出
  5. if(num[mid]>t){
  6. right=mid-1; //t在左区间,所以答案在[left, mid-1]
  7. }else if(num[mid]<t){
  8. left=mid+1; //t在右区间,所以答案在[mid+1,right]
  9. }else{ //num[mid]==t,找到答案
  10. return mid;
  11. }
  12. }
  13. return -1; //没有找到目标值
  14. }

声明一下,计算 mid 时需要防止溢出,代码中left+(right-left)/2就和(left+right)/2的结果相同,但是有效防止了left和right太大直接相加导致溢出

总结

因为我们初始化right=size-1
所以决定了我们的查找区间是[left, right]
所以决定了while (left<=right)
同时也决定了left=mid+1right=mid-1

因为我们只需找到一个t的索引即可
所以当num[mid]==target时可以立即返回

模板二(寻找左侧边界的二分查找)

  1. int check(int num[],int size,int t){
  2. int left=0,right=size;
  3. while(left<right){ //每次循环的查找区间是[left,right)左闭右开
  4. int mid=(left+right)/2;
  5. if(nums[mid]<t){
  6. left=mid+1;
  7. }else if(nums[mid]>t){
  8. right=mid;
  9. }else{ //两个条件可以合并为一个
  10. right=mid;
  11. }
  12. }
  13. return left; //终止的条件是left==right,此时搜索区间[left,left)为空,可以正确终止
  14. }

总结

因为我们初始化right=size
所以决定了我们的查找区间是[left, right)
所以决定了while (left < right)
同时也决定了left=mid+1right=mid

因为我们需找到t的最左侧索引
所以当num[mid]==t时不要立即返回
而要收紧右侧边界以锁定左侧边界

模板三(寻找右侧边界的二分查找)

  1. int check(int num[],int size,int t){
  2. int left=0,right=size;
  3. while(left<right){ //每次循环的查找区间是[left, right)
  4. int mid=(left+right)/2;
  5. if(num[mid]<t){
  6. left=mid+1;
  7. }else if(nums[mid]>target){
  8. right=mid;
  9. }else{ //两个条件可以合并为一个
  10. left=mid+1;
  11. }
  12. }
  13. return left-1; //终止的条件是left==right,此时搜索区间(right,right]为空,可以正确终止
  14. }

总结

因为我们初始化right=size
所以决定了我们的查找区间是[left, right)
所以决定了while (left < right)
同时也决定了left=mid+1right=mid

因为我们需找到t的最右侧索引
所以当num[mid]==t时不要立即返回
而要收紧左侧边界以锁定右侧边界

又因为收紧左侧边界时必须left=mid+1
所以最后无论返回left还是right,必须减一

对于寻找左右边界的二分搜索,常见的手法是使用左闭右开的查找区间,我们还根据逻辑将查找区间全都统一成了两端都闭,便于记忆,只要修改两处即可变化出三种写法:

  1. int check(int num[],int size,int t){
  2. int left=0,right=size-1;
  3. while(left<=right){
  4. int mid=left+(right-left)/2;
  5. if(num[mid]<t){
  6. left=mid+1;
  7. }else if(num[mid]>t){
  8. right=mid-1;
  9. }else{
  10. return mid;// 直接返回
  11. }
  12. }
  13. return -1;
  14. }
  15. int check(int num[],int size,int t){
  16. int left=0,right=size-1;
  17. while(left<=right){
  18. int mid=left+(right-left)/2;
  19. if(num[mid]<t){
  20. left=mid+1;
  21. }else if(num[mid]>t) {
  22. right=mid-1;
  23. }else{
  24. right=mid-1; //别返回,锁定左侧边界
  25. }
  26. }
  27. if (left>=size||num[left]!=t){
  28. return -1; // 最后要检查 left 越界的情况
  29. }
  30. return left;
  31. }
  32. int check(int num[],int size,int t){
  33. int left=0,right=size-1;
  34. while(left<=right){
  35. int mid=left+(right-left)/2;
  36. if(num[mid]<t) {
  37. left=mid + 1;
  38. }else if(num[mid]>t) {
  39. right=mid-1;
  40. }else{
  41. left=mid+1;// 别返回,锁定右侧边界
  42. }
  43. }
  44. if (right<0||num[right]!=t){
  45. return -1;// 最后要检查 right 越界的情况
  46. }
  47. return right;
  48. }

如果以上内容你都能理解,那么恭喜你,二分查找也不过如此。。。

二分答案

什么是二分答案?
答案属于一个区间,当这个区间很大时,暴力超时。但重要的是——这个区间是对题目中的某个量有单调性的,此时,我们就会二分答案。每一次二分会做一次判断,看是否对应的那个量达到了需要的大小。
判断:根据题意写个check函数,如果满足check,就放弃右半区间(或左半区间),如果不满足,就放弃左半区间(或右半区间),一直往复,直至到最终的答案。

如何判断一个题是不是用二分答案做的呢?

典型特征:

求...最大值的最小求...最小值的最大
1.求...最大值的最小时,我们二分答案(即二分最大值)的时候,判断条件满足后,尽量让答案往前来(即:让right=mid
2.求...最小值的最大时,我们二分答案(即二分最小值)的时候,判断条件满足后,尽量让答案往后走(即:让left=mid

1、答案在一个区间内(一般情况下,区间会很大,暴力超时)

2、直接搜索不好搜,但是容易判断一个答案可行不可行

3、该区间对题目具有单调性,即:在区间中的值越大或越小,题目中的某个量对应增加或减少。

[NOIP2015 提高组] 跳石头 - 洛谷

我们二分的是最短距离,通过二分将这个最短距离(答案)最大化。那我们判断的时候肯定要保证mid是最短距离

我们要求抽过石头剩下的石头中,两个石头间的最短距离为mid,那就要保证剩下的任意两个间距都要大于等于mid。要保证这个,那就只能挑间距大于等于mid的石头跳,中间的石头都将会被抽走。

最后,计数可以被抽走的石头。如果可以被抽走的石头个数小于等于需要抽的M个了,就说明满足条件。因为:既然抽了小于M个都能满足剩下的石头中,两石头间的距离都大于等于mid了,那抽M个,更能满足!       

  1. #include<cstdio>
  2. using namespace std;
  3. const int N=50005;
  4. int d[N];
  5. int main(){
  6. int L,n,m,ans,x,left,right,mid;
  7. scanf("%d%d%d",&L,&n,&m);
  8. for(int i=0;i<n;i++){
  9. scanf("%d",&d[i]);
  10. }
  11. left=0;right=L;
  12. while(left<=right){
  13. mid=(left+right)/2;
  14. ans=0;
  15. x=0;
  16. d[n]=L;
  17. for(int i=0;i<=n;i++){
  18. if(d[i]-x<mid){
  19. ans++; //两石头间距离小于mid,mid不是最短距离,不满足,移走该石头
  20. }else{
  21. x=d[i]; //符合,跳过去
  22. }
  23. }
  24. if(ans<=m){
  25. left=mid+1; //要的是距离的最大,所以尽可能地往右走
  26. }else{
  27. right=mid-1;
  28. }
  29. }
  30. printf("%d",left-1);
  31. return 0;
  32. }

Pie - HDU 1969          

当你的中间取值都满足条件时,说明比它小的都满足条件,那么此时下界就变为中间取值,当中间取值不满足条件时,说明比它大的都不满足条件,那么这时候上界就变为中间取值。

由于题目输入给的是派的半径,因此我们需要转化为体积(高为1,面积就是体积)。

那么如何判断中间值是否满足条件呢???
求出每个派最多能分的人数(即派的面积除以中间值取整),再将人数相加,比较此时可分得总人数是否大于朋友数加自己(即F+1),若大于则中间值满足条件,更改下界值,反之亦然。

注意将PI放在输出时乘入可提高精度。PI=acos(-1)。

  1. #include<cstdio>
  2. #include<cmath>
  3. using namespace std;
  4. const double PI=acos(-1.0);
  5. const int N=10005;
  6. double a[N];
  7. int main(){
  8. int m;
  9. scanf("%d",&m);
  10. for(int i=1;i<=m;i++){
  11. int n,f,ans;
  12. double left,right,mid,v;
  13. scanf("%d%d",&n,&f);
  14. f++;right=0;
  15. for(int j=1;j<=n;j++){
  16. scanf("%lf",&a[j]);
  17. a[j]=PI*a[j]*a[j];
  18. if(right<a[j]){
  19. right=a[j];
  20. }
  21. }
  22. left=0;
  23. while(right-left>0.00001){ //注意精度要求,很重要!!!
  24. mid=(left+right)/2;
  25. ans=0;
  26. for(int k=1;k<=n;k++){
  27. ans+=(int)(a[k]/mid);
  28. }
  29. if(ans>=f){
  30. left=mid;
  31. }else{
  32. right=mid;
  33. }
  34. }
  35. printf("%.4lf\n",left);
  36. }
  37. return 0;
  38. }

二分中最痛苦的问题就是边界问题。。。。。

至于边界问题,请待下回分解!!!

                                                                                                     

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/333238
推荐阅读
相关标签
  

闽ICP备14008679号