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

这篇具有很好参考价值的文章主要介绍了C++二分算法(二分查找&二分答案)细节详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

二分查找

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

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

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

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

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

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

声明一下,计算 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时可以立即返回

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

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

总结

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

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

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

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

总结

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

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

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

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

int check(int num[],int size,int t){
    int left=0,right=size-1; 
    while(left<=right){
        int mid=left+(right-left)/2;
        if(num[mid]<t){
            left=mid+1;
        }else if(num[mid]>t){
            right=mid-1; 
        }else{
            return mid;// 直接返回
        }
    }
    return -1;
}

int check(int num[],int size,int t){
    int left=0,right=size-1;
    while(left<=right){
        int mid=left+(right-left)/2;
        if(num[mid]<t){
            left=mid+1;
        }else if(num[mid]>t) {
            right=mid-1;
        }else{
            right=mid-1;		//别返回,锁定左侧边界
        }
    }
    
    if (left>=size||num[left]!=t){
    	return -1;				// 最后要检查 left 越界的情况
	}       
    return left;
}


int check(int num[],int size,int t){
    int left=0,right=size-1;
    while(left<=right){
        int mid=left+(right-left)/2;
        if(num[mid]<t) {
            left=mid + 1;
        }else if(num[mid]>t) {
            right=mid-1;
        }else{ 
            left=mid+1;// 别返回,锁定右侧边界
        }
    }
    if (right<0||num[right]!=t){
    	return -1;// 最后要检查 right 越界的情况
	}
    return right;
}

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

二分答案

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

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

典型特征:

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

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

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

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

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

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

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

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

#include<cstdio>
using namespace std;

const int N=50005;
int d[N];
int main(){
	int L,n,m,ans,x,left,right,mid;
	
	scanf("%d%d%d",&L,&n,&m);
	
	for(int i=0;i<n;i++){
		scanf("%d",&d[i]);
	}
	
	left=0;right=L;
	
	while(left<=right){
		mid=(left+right)/2;
		ans=0;
		x=0;
		d[n]=L;
		for(int i=0;i<=n;i++){
			if(d[i]-x<mid){
				ans++;				//两石头间距离小于mid,mid不是最短距离,不满足,移走该石头
			}else{
				x=d[i];				//符合,跳过去
			} 
		}
		if(ans<=m){
			left=mid+1;				//要的是距离的最大,所以尽可能地往右走
		}else{
			right=mid-1;
		}
	}
	printf("%d",left-1);
	return 0;
} 

Pie - HDU 1969          

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

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

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

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

#include<cstdio>
#include<cmath>
using namespace std;

const double PI=acos(-1.0);
const int N=10005;
double a[N];
int main(){
	int m;
	scanf("%d",&m);
	for(int i=1;i<=m;i++){
		int n,f,ans;
		double left,right,mid,v;
		scanf("%d%d",&n,&f);
		f++;right=0;
		for(int j=1;j<=n;j++){
			scanf("%lf",&a[j]);
			a[j]=PI*a[j]*a[j];
			if(right<a[j]){
				right=a[j];
			}
		}
		left=0;
		while(right-left>0.00001){            //注意精度要求,很重要!!!
			mid=(left+right)/2;
			ans=0;
			for(int k=1;k<=n;k++){
				ans+=(int)(a[k]/mid);
			}
			if(ans>=f){
				left=mid;
			}else{
				right=mid;
			}
		}
		printf("%.4lf\n",left);
	}
	return 0;
}

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

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

                                                                                                     

 文章来源地址https://www.toymoban.com/news/detail-472446.html

到了这里,关于C++二分算法(二分查找&二分答案)细节详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++二分查找算法的应用:最长递增子序列

    C++二分算法应用:最长递增子序列 二分查找算法合集 单调映射 点击下载源码 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子

    2024年02月06日
    浏览(43)
  • 【动态规划】【二分查找】C++算法 466 统计重复个数

    视频算法专题 动态规划汇总 二分查找 定义 str = [s, n] 表示 str 由 n 个字符串 s 连接构成。 例如,str == [“abc”, 3] ==“abcabcabc” 。 如果可以从 s2 中删除某些字符使其变为 s1,则称字符串 s1 可以从字符串 s2 获得。 例如,根据定义,s1 = “abc” 可以从 s2 = “abdbec” 获得,仅需

    2024年01月23日
    浏览(50)
  • C++二分查找算法:132 模式解法二枚举2

    二分查找算法合集 包括题目及代码 C++二分查找算法:132 模式解法一枚举3 C++二分查找算法:132 模式解法二枚举2 代码简洁 C++二分查找算法:132 模式解法三枚举1 性能最佳 C++单调向量算法:132 模式解法三枚举1 代码更简洁 C++二分查找算法:132模式枚举3简洁版 代码简洁,性能

    2024年02月05日
    浏览(45)
  • 【手撕数据结构】二分查找(好多细节)

    🌈键盘敲烂,年薪30万🌈 目录 普通版本的二分查找: right只负责控制边界(少了两次比较): 时间复杂度更稳定的版本: BSLeftmost: BSRightmost:   🏸细节1:循环判定条件是left = right ⭐细节2:mid = (left + right ) 1 原因见代码注释 改动1:while条件是left right 改动2:right = nums.len

    2024年02月05日
    浏览(47)
  • 【动态规划】【二分查找】【C++算法】730. 统计不同回文子序列

    视频算法专题 动态规划汇总 二分查找算法合集 给你一个字符串 s ,返回 s 中不同的非空回文子序列个数 。由于答案可能很大,请返回对 109 + 7 取余 的结果。 字符串的子序列可以经由字符串删除 0 个或多个字符获得。 如果一个序列与它反转后的序列一致,那么它是回文序列

    2024年01月19日
    浏览(50)
  • 算法与人生 揭秘C语言中高效搜索的秘诀——二分查找算法详解

    引言,少年们,大家好。在这里祝大家元旦快乐,我是博主 那一脸阳光 ,今天来介绍二分查找 在计算机科学领域,搜索算法是数据处理和问题解决的重要工具之一。其中,**二分查找算法(Binary Search)**以其卓越的时间复杂度和简洁高效的实现,在众多搜索算法中脱颖而出

    2024年01月17日
    浏览(59)
  • C++二分查找算法:有序矩阵中的第 k 个最小数组和

    二分查找算法合集 C++二分查找算法:查找和最小的 K 对数字 十分接近m恒等于2 给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。 你可以从每一行中选出 1 个元素形成一个数组。返回所有可能数组中的第 k 个 最小 数组和。 示例 1: 输入:

    2024年02月05日
    浏览(50)
  • Offer必备算法_二分查找_八道力扣OJ题详解(由易到难)

    目录 二分查找算法原理 ①力扣704. 二分查找 解析代码 ②力扣34. 在排序数组中查找元素的第一个和最后一个位置 解析代码 ③力扣69. x 的平方根  解析代码 ④力扣35. 搜索插入位置 解析代码 ⑤力扣852. 山脉数组的峰顶索引 解析代码 ⑥力扣162. 寻找峰值 解析代码 ⑦力扣153. 寻

    2024年02月19日
    浏览(40)
  • 问题 C: 二分查找右侧边界(C++)(二分查找)

    1.题目描述 2.AC 问题 C: 二分查找右侧边界 时间限制: 1.000 Sec  内存限制: 128 MB 提交 状态 题目描述 请在一个有序不递减的数组中(数组中的值有相等的值),采用二分查找,找到最后1次出现值x的位置,如果不存在x请输出-1。 请注意:本题要求出q个x,每个x在数组中第一

    2024年02月03日
    浏览(45)
  • 【算法系列篇】二分查找——这还是你所知道的二分查找算法吗?

    在生活中,我们往往会遇到在数组中查找某个确定的元素的时候,通常我们会选择使用暴力解法,这样虽然简单,但是时间复杂度是O(N),时间效率比较低。那么是否有方法可以使得在具有二段性的数组中找某一特定的元素的时间复杂度低于0(N)呢?答案是肯定的,当我们可以

    2024年02月11日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包