三分算法:原理、实现与实战应用

1. 引言

三分算法(Ternary Search)是一种用于在单峰函数 或单谷函数上寻找极值点的搜索算法。与二分查找要求序列单调不同,三分算法适用于函数值先增后减(或先减后增)的场景,通过不断缩小区间来逼近极值点。

三分算法在算法竞赛、数值优化、工程调参等领域有着广泛的应用,例如求解凸函数的最小值、寻找最优参数、计算几何中的最值问题等。本文将系统讲解三分算法的原理、实现方式以及典型应用场景。

2. 算法原理

2.1 适用场景

三分算法适用于单峰函数 (先严格递增后严格递减)或单谷函数(先严格递减后严格递增)。所谓单峰,是指函数在定义域内只有一个极值点,且极值点两侧分别单调。

常见的适用场景包括:

  • 二次函数求极值
  • 距离类函数求最值
  • 几何问题中的最优位置搜索
  • 参数调优中的损失函数最小化

2.2 核心思想

假设我们要在区间 [l, r] 上寻找单峰函数 f(x) 的最大值。三分算法的核心思想是:

  1. 取区间的两个三等分点 m1 和 m2,其中 m1 = l + (r - l) / 3,m2 = r - (r - l) / 3。
  2. 比较 f(m1) 与 f(m2) 的大小:
    • 若 f(m1) < f(m2),说明峰值在 [m1, r] 区间内,令 l = m1。
    • 若 f(m1) > f(m2),说明峰值在 [l, m2] 区间内,令 r = m2。
    • 若相等,峰值在 [m1, m2] 之间,可令 l = m1, r = m2。
  3. 重复上述过程,直到区间长度小于预设精度。

每次迭代区间长度缩小为原来的 2/3,因此时间复杂度为 O(log_{3/2} n)。

2.3 与二分查找的区别

对比项 二分查找 三分算法
适用条件 单调序列 单峰/单谷函数
每次比较 1 个中点 2 个三等分点
区间缩小比例 1/2 2/3
目标 查找特定值 查找极值点

3. 代码实现

3.1 整数域三分(求最大值)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

// 单峰函数,求最大值
int f(int x) {
    return -(x - 5) * (x - 5) + 10;  // 顶点在 x=5,最大值为 10
}

int ternarySearch(int l, int r) {
    while (r - l > 2) {  // 整数域保留 2 的容差
        int m1 = l + (r - l) / 3;
        int m2 = r - (r - l) / 3;
        if (f(m1) < f(m2)) {
            l = m1;
        } else {
            r = m2;
        }
    }
    // 在 [l, r] 内暴力枚举找最大值
    int ans = f(l);
    for (int i = l + 1; i <= r; i++) {
        ans = max(ans, f(i));
    }
    return ans;
}

int main() {
    cout << ternarySearch(0, 10) << endl;  // 输出 10
    return 0;
}

3.2 实数域三分(求最小值)

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

// 单谷函数,求最小值
double f(double x) {
    return (x - 3.0) * (x - 3.0) + 2.0;  // 顶点在 x=3,最小值为 2
}

double ternarySearch(double l, double r) {
    const double eps = 1e-9;  // 精度控制
    while (r - l > eps) {
        double m1 = l + (r - l) / 3.0;
        double m2 = r - (r - l) / 3.0;
        if (f(m1) < f(m2)) {
            r = m2;  // 最小值在 [l, m2]
        } else {
            l = m1;  // 最小值在 [m1, r]
        }
    }
    return f(l);
}

int main() {
    printf("%.10f\n", ternarySearch(0.0, 10.0));  // 输出 2.0000000000
    return 0;
}

【模板】三分 / 函数 / ICPC 2010 Chengdu R Error Curves

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=100005,inf=INT_MAX;
const double eps=1e-9;
int n;
int a[N],b[N],c[N];
inline double f(double x)
{
	double maxn=-inf;
	for(int i=1;i<=n;i++){
		maxn=max(maxn,a[i]*x*x+b[i]*x+c[i]);
	}
	return maxn;
}

signed main()
{
	int t;
	cin>>t;
	while(t--)
    {
		cin>>n;
		for(int i=1;i<=n;i++)
        {
			cin>>a[i]>>b[i]>>c[i];
		}	
		double l=0,r=1000;
		while(r-l>eps)
        {
			double m1=(2*l+r)/3;
			double m2=(l+2*r)/3;
			if(f(m1)<f(m2))
            {
				r=m2;
			}
            else
            {
				l=m1;
			}
		}
		printf("%.4lf\n",f(l));
	}
	return 0;
}

4. 注意事项与优化

4.1 函数必须严格单峰/单谷

如果函数存在多个极值点或平台区间,三分算法可能失效。此时需要先分析函数性质,或改用其他方法(如模拟退火、爬山法)。

4.2 时间复杂度

每次迭代区间缩小 2/3,迭代次数约为 log_{3/2}((r-l)/eps)。对于 1e9 的整数区间,约需 50 次迭代;对于实数域,迭代次数取决于精度要求。

三分套三分模板

SCOI2010 传送带

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
constexpr double eps=1e-9;
double ax,ay,bx,by;
double cx,cy,dx,dy;
double ex,ey,fx,fy;
double p,q,r;
double dis(double x,double y,double xx,double yy)
{
    return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}
double FF(double y);
double F(double x)
{
    ex=ax+(bx-ax)*x,ey=ay+(by-ay)*x;
    double l=0,r=1;
    double lm,rm;
    while(r-l>eps)//第二个三分
    {
        lm=(2*l+r)/3;
        rm=(l+2*r)/3;
        if(FF(lm)<FF(rm))
        {
            r=rm;
        }
        else
        {
            l=lm;
        }
    }
	return FF((l+r)/2);
}
double FF(double y)
{
    fx=cx+(dx-cx)*y,fy=cy+(dy-cy)*y;
    return dis(ax,ay,ex,ey)/p+dis(ex,ey,fx,fy)/r+dis(fx,fy,dx,dy)/p;
}
int main()
{
    cin>>ax>>ay>>bx>>by;
    cin>>cx>>cy>>dx>>dy;
    cin>>p>>q>>r;
    double l=0,r=1;
    double lm,rm;
    while(r-l>eps)//第一个三分
    {
        lm=(2*l+r)/3;
        rm=(l+2*r)/3;
        if(F(lm)<F(rm))
        {
            r=rm;
        }
        else
        {
            l=lm;
        }
    }
    printf("%.2lf",F((l+r)/2));
    return 0;
}

5. 总结

三分算法是一种简洁而强大的极值搜索工具,适用于单峰/单谷函数,通过不断缩小区间逼近极值点。本文系统梳理了以下要点:

  • 原理 :每次取两个三等分点 m1、m2,比较函数值后舍弃一侧区间,每次迭代区间缩小为原来的 2/3,时间复杂度为 O(log_{3/2} n)。
  • 实现 :整数域需保留容差并在小范围内暴力枚举;实数域需设置精度 eps(通常 1e-7 ~ 1e-12)。
  • 注意事项:函数必须严格单峰/单谷,否则算法可能失效;需区分整数域与实数域的实现细节。
相关推荐
思茂信息2 小时前
CST软件UV Slice切片后可直接删除指定一侧实体
算法·uv·emc·emi·cst·仿真软件
INGNIGHT3 小时前
944 · 最大子矩阵(前缀和)
数据结构·c++·算法
All for pursuit.3 小时前
【动态规划-6】96.不同的二叉搜索树
数据结构·c++·算法·leetcode·动态规划
rou4 小时前
Attention(注意力机制)
算法
信奥卷王4 小时前
[GESP202609 六级] 数组划分
数据结构·算法
牧羊人.3334 小时前
动手学深度学习 07|Resnet网络与迁移学习
网络·人工智能·深度学习·神经网络·算法·cnn·迁移学习
All for pursuit.5 小时前
【并查集-2】399.除法求值
数据结构·c++·算法·leetcode
迪丽热爱5 小时前
一、C语言概述与程序结构
c语言·开发语言·算法
码爸6 小时前
动态规划介绍
算法·动态规划