c++二分算法

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

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

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

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

二分查找


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

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

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

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


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

int find(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)>>1);//等同于(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)>>1 就和 (left+right)/2 的结果相同,但是有效防止了left和right太大直接相加导致溢出

但这有个缺点,就是看到这个数就返回,这个数不一定是第一次出现的或最后一次出现的

小结一下

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

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

他返回的是这个元素第一次出现的位置

int find(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+1 right=mid

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

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

他返回的是这个元素最后一次出现的位置

int find(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+1 和 right=mid

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

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

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

二分答案


什么是二分答案?


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

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

典型特征:

求...最大值的最小 、 求...最小值的最大。


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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值