当前位置: 首页 > news >正文

二分三分算法详解, 模板与临界条件分析

文章目录

    • 0x00 序
    • 0x01 整数域上的二分
      • 0x0101 查找可行解的最左端点
      • 0x0102 查找可行解的最右端点
    • 0x02 实数域上的二分
    • 0x03 三分
      • 0x0310 实数域上三分
      • 0x0302 整数域上三分
      • 0x0303 三分套三分

0x00 序

二分, 就是在有序的序列中不断对半分,从而实现 l o g log log 级别的查询速度。

二分从简单的应用角度来说, 分为如下:

  1. 整数域二分
    1. 查找可行解区间的最左端点
    2. 查找可行解区间的最右端点
  2. 实数域二分

同样的, 三分就是每次选取三分之一段, 复杂度为 l o g 3 log_3 log3 级别. 主要用于寻找单峰函数极值. 分为凸函数和凹函数两类, 实数域和整数域上两类…

学习是开放的, 推荐一个我常常跟随学习的大佬的 博客, 可以看看. 对三分法讲的很细.

0x01 整数域上的二分

0x0101 查找可行解的最左端点

在一个有序序列中, 如 [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] [0,1,2,3,4,5,6,7,8,9] [0,1,2,3,4,5,6,7,8,9] 找到所有 5 ≤ x 5 \leq x 5x 的数中, 最左边的一个, 即找到例子中的 a [ 5 ] a[5] a[5].

我们定义两个变量 l = 0r = 9, 代表初始的这个数组范围, 在二分的过程中会不断缩小. 宗旨就是: 让 [ l , r ] [l,r] [l,r] 代表的区间尽可能的是我们想要的区间.

每次对半查询中, 定义一个 mid=l+r>>2, 然后查看 a[mid] 与目标值 5 5 5 的关系。有以下几种情况:

  1. a[mid]>=5, check 返回 true:这时 m i d mid mid 是可取的, 本着让 [ l , r ] [l,r] [l,r] 代表的区间尽可能的是我们想要的区间的原则, 让 r = m i d r=mid r=mid, 保证 r r r 所在的位置一定是满足 5 ≤ a [ r ] 5 \leq a[r] 5a[r] 的.
  2. a[mid]<5, check 返回 false:这时 m i d mid mid 是可取的, 让 l = m i d + 1 l=mid+1 l=mid+1, 因为此时 m i d mid mid 所在的位置一定是不满足 5 ≤ a [ r ] 5 \leq a[r] 5a[r] 的, 就要让 l l l 再往左去一个, 让它尽可能满足.

代码如下:

int bsearch_1(int l, int r)//求满足要求的最小值,求左端点
{
	while (l < r)
	{
        int mid = l + (r - l) / 2; //int mid = l + r >> 1;有可能爆ll
		if (check(mid)) r = mid;   
		else l = mid + 1;
	}
	return l;
}

0x0102 查找可行解的最右端点

在一个有序序列中, 如 [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] [0,1,2,3,4,5,6,7,8,9] [0,1,2,3,4,5,6,7,8,9] 找到所有 x < 5 x < 5 x<5 的数中, 最左边的一个, 即找到例子中的 a [ 4 ] a[4] a[4].

这里有点区别, 每次对半查询中, 定义 mid = (l + r + 1) >> 2.有以下几种情况:

  1. a[mid]>=5, check 返回 false:这时 m i d mid mid 是不可取的, 让 r r r再往左去一个, 即 r = m i d − 1 r=mid-1 r=mid1, 保证 r r r 所在的位置尽可能是满足 5 ≤ a [ r ] 5 \leq a[r] 5a[r] 的.
  2. a[mid]<5, check 返回 true:这时 m i d mid mid 是可取的, 让 l = m i d l=mid l=mid.

具体为什么要在 定义 m i d mid mid 时加一呢?

讨论一个临界条件:

l = 4 , r = 5 l=4, r=5 l=4,r=5, 如果不加一, m i d = 4 mid=4 mid=4, check 返回 true -> l = mid = 4, 发现会进入死循环. 让 m i d mid mid 往上偏一位, 就是为了避免这种死循环. 同时让两种情况的推出条件统一成了 while(l < r), 退出时 l l l 就是要找的答案.

代码如下:

int bsearch_2(int l, int r)//求满足要求的最大值,求右端点
{
	while (l < r)
	{
		int mid = l + r + 1 >> 1;
		if (check(mid)) l = mid;
		else r = mid - 1;
	}
	return l;
}

0x02 实数域上的二分

实数域上的二分, 相对就比较简单, 如果选取上面的模板, 只需要改退出条件为 while(r - l < eps), e p s eps eps 为定义的最小值, 一般为 1 e − 7 1e-7 1e7, 视题目误差要求而定. 对 m i d mid mid 的选择和 l , r l,r l,r 的赋值就更为宽松, 因为没有了临界条件的考虑.

const double eps =1e-7;        //精度。
while(r - l > eps){
      double mid = l + (r - l) / 2;
      if (check(mid)) r = mid;
      else	l  = mid;
}

还有一种更为通用的写法, 因为这种循环不超过几百次. 完全可以写一个循环, 直接跑上一千次. 完全在大多数的题目时间限制里.

int cnt = 1000;
while(cnt--){
	  double mid = l + (r - l) / 2;
      if (check(mid)) r = mid;
      else	l  = mid;
}

0x03 三分

0x0310 实数域上三分

以凸函数为例子, 凹函数改一下 check 就行.

对于一个单峰函数 (例如: y = − x 2 y=-x^2 y=x2), 如果想要找到它的顶点 (当然, 已知在 x = 0 x=0 x=0), 可以先定义 l = − i n f , r = i n f l=-inf, r=inf l=inf,r=inf, i n f inf inf 只要确保包含了答案就行.

每次取两个三等分点 m i d l midl midl m i d r midr midr, 当函数值 f ( m i d l ) < f ( m i d r ) f(midl) < f(midr) f(midl)<f(midr) 的时候, 就可以判断极值在 [ l , m i d r ] [l,midr] [l,midr] 这个区间内, 画个图就能看出来了. 更具体的, 大家可以手玩一下 !

在这里插入图片描述
当然, 既然在实数域上了, 当然也是可以直接暴力循环一千次的. 代码就不贴了, 一定不是我懒着写.

想一想, 这份代码好像能通用地处理当区间 [ l , r ] [l,r] [l,r]为单调的, 即极值在两端. 主要是因为你不用真的找到两端, 只要找到两端加减 e p s eps eps 就行. 接下来的整数域就不行, 因为 e p s eps eps 的概念在整数域不存在, 你就要找到准确的那个数.

//以凸函数为例子, 凹函数改一下 check 中的  小于号 -> 大于号
while (r - l > eps) {
  ld midl=(l*2+r)/3;
  ld midr=(l+r*2)/3;//三等分法
  if (f(midl) < f(midr))
    l = midl;
  else
    r = midr;
}

// 这里 check 反过来写了, 注意一下, 这个写法反正我不用, 就贴一下代码, 注意问题注释里写了
while (r - l > eps) {
    ld mid = l + (r - l) / 2;
    ld midl = mid - eps;
    ld midr = mid; //近似分割法, 会被卡精度
    if (check(midl) > check(midr))
        r = mid;//不能 r=midr, 当 r-l=2*eps, 会有 l=midl,r=midr 出现
    else
        l = mid;
}

0x0302 整数域上三分

直接贴代码, 思想是一样的, 主要是特殊处理两端. 注意 while 里面条件的变化.

int bin3(int l, int r)
{
    if (l > r) return -1;
    int res = max(f(l), f(r));
    while (l <= r) {
        int m = (l + r) >> 1, mm = (m + r) >> 1;
        int fm = f(m), fmm = f(mm);
        if (fm <= fmm) l = m + 1;
        else r = mm - 1;
        res = max(res, max(fm, fmm));
    }
    return res;
}

0x0303 三分套三分

主要处理这种问题(如图), 找它的极值点.

在这里插入图片描述
对于两个变量的凹 / 凸函数(一个圆锥形), 先固定 x x x 三分 y y y , 再三分 x x x. 因为对于其的任意切片, 一定是一个凹 / 凸函数, 且极值点和三维状态一样.

double run(double x) // 固定x,三分y
{
    ....
    while (r - l > eps)
    {
        mid = (l + r) / 2;
        if (f(x, mid - eps) > f(x, mid + eps))
            l = mid;
        else
            r = mid;
    }
    return f(x, mid);
}
int main()
{
	...
    while (r - l > eps)
    {
        mid = (l + r) / 2; // 三分x
        if (run(mid - eps) > run(mid + eps))
            l = mid;
        else
            r = mid;
    }
    printf("%.6f\n", run(mid));
}

相关文章:

  • Android开发:应用DeepSeek官方Api在App中实现对话功能
  • 智能制造方案精读:117页MES制造执行系统解决方案【附全文阅读】
  • vue webSocket
  • 腾势品牌欧洲市场冲锋,科技豪华席卷米兰
  • CSI-PVController-claimWorker
  • 【Unity精品源码】Ultimate Character Controller:高级角色控制器完整解决方案
  • Go语言Slice切片底层
  • 51c大模型~合集65
  • 【开发记录】服务外包大赛记录
  • CF985G Team Players
  • 即梦+剪映:三国演义变中国好声音制作详解!
  • 游戏引擎学习第221天:(实现多层次过场动画)
  • 局部路由守卫
  • 【数据集】中国各省低空经济及无人机相关数据集(1996-2025年2月)
  • 李宏毅NLP-3-语音识别part2-LAS
  • SylixOS 下优先级反转与解决方案
  • transformers v4.51.1正式发布!Llama 4多项关键修复,深度学习玩家速更!
  • spring boot 引入fastjson,com.alibaba.fastjson不存在(Springboot-测试项目)
  • gevent 高并发、 RabbitMQ 消息队列、Celery 分布式的案例和说明
  • 论文精度:BoltzFormer:基于Boltzmann采样的动态稀疏注意力机制在小物体图像分析中的应用
  • 费高云不再担任安徽省人民政府副省长
  • 牛市早报|中方调整对美加征关税措施,五部门约谈外卖平台企业
  • 央媒评网红质疑胖东来玉石定价暴利:对碰瓷式维权不能姑息
  • 福建厦门市副市长、市公安局局长陈育煌出任吉林省公安厅厅长
  • 小米SU7 Ultra风波升级:数百名车主要求退车,车主喊话雷军“保持真诚”
  • 全国重点网络媒体和网络达人走进沧州,探寻“文武双全”的多重魅力