算法:三分查找算法

原理

三分法的原理也很简单,和二分法几乎一模一样,只不过我们分隔区间的时候,不是将区间一分为二,而是一分为三。之后,我们同样通过缩小区间的方法来确定要查找的值所在。

但是:既然分成两份就能解决问题,我们为什么要分成三份呢?

在回答这个问题之前,我们先来看另一个问题。在数学上,二分法究竟解决了一个什么问题?

还记得二分法使用的前提吗?数组必须是有序的,所以二分法其实解决的是单调函数的求解问题。只要数组是有序的,根据函数的定义就可以看做是一个将数组下标映射到数组取值的函数。显然,这是一个单调函数,我们通过二分法查找其中的一个元素v,本质是查找

在这里插入图片描述
的解。

所以,二分法使用的场景是单调函数,也就是一次函数。那如果我要搜索二次函数的最小值,用二分法可行吗?
在这里插入图片描述
显然不可行,因为我们在取完mid之后, 并不知道答案可能出现在左右哪个区间。

这个时候就需要三分法出场了。
在这里插入图片描述
三分法会将区间分成三份,这个我们都已经知道了。分成三份,自然需要两个端点。这两个端点各有一个值,我们分别叫做m1和m2。我们要求的是函数的最小值,所以我们要想极值逼近。

但是我们有两个中间点,该怎么逼近呢?

我们直接根据函数图像来分析,根据上图我们可以看出来,m1和m2的函数值和它们距离极值点的远近是有关系的。离极值点越近,函数值越小(也有可能越大,视函数而定)。在上图当中,
在这里插入图片描述
,所以m2离极值点更近。我们要缩小区间范围,逼近极值点,所以我们应该让
在这里插入图片描述
这里有一点小问题,我们怎么确定极值点在m2和m1中间呢?万一在m2的右侧该怎么办呢?
在这里插入图片描述
我们画出图像来看,这种情况其实并没有区别,我们只会抛弃区间

在这里插入图片描述
,并不会影响极值点。

会不会极值点在m1左侧呢?这是不可能的,因为如果极值点在m1左侧,那么m2距离极值点一定比m1远,这种情况下m2处的函数值是不可能小于m1的。

也就是说,三分法的精髓在于,每次通过比较两个值的大小,缩小三分之一的区间。直到最后区间的范围小于我们设置的阈值为止

过程

在这里插入图片描述

  • 使用两个点将之分为三段:
  • midl = left + (right - left)/3;
  • midr= right - (right - left)/3;
  • 如果midl比midr更加靠近最值点,我们就令 right = midr- 1; 【舍弃远离的那一段】
  • 如果midr比midl更加靠近罪之颠,令left = midl + 1; 【舍弃远离的那一段】

假如是取最大值,则midr和midl中谁大谁更靠近最终的结果,于是最终选择的区间一定要包含较大者;同样取最小值也是这样的,也就是:

  • 若f(m1) < f(m2),说明极值点位于[m1, r]区间内,可以不必再考虑[l, m1]区间;
  • 若f(m1) > f(m2),说明极值点位于[l, m2]区间内,可以不必再考虑[m2, r]区间

这样,每一轮迭代都会把查找范围限制在原来的2/3,直到最终逼近极值点,即l和r之间的差值接近无穷小。容易推导出三分查找的时间复杂度为:

T(n) = T(2n / 3) + 1 = O(log3n)

代码实现

   private static int trisection_search(int[] arr){
        if (arr == null){
            return -1;
        }

        int left = 0;
        int right = arr.length - 1;
        while (left <= right ){
            int midl = left + (right - left)/3;
            int midr = right - (right - left)/3;

            if (arr[midl] == arr[midr]){
                return midl;
            }else if (arr[midl] > arr[midr]){
                right = midr - 1;
            }else {
                left = midl + 1;
            }

        }


        return -1;
    }
public class TreeNode {
    // 数组中没有重复的元素
    private static int trisection_search(int[] arr){
        if (arr == null){
            return -1;
        }

        int left = 0;
        int right = arr.length;
        while (left < right ){
            int midl = left + (right - left)/3;
            int midr = right - (right - left)/3;


            if (arr[midl] > arr[midr]){
                right = midr - 1;
            }else{
                left = midl + 1;
            }

        }


        return left;
    }



    public static void main(String[] args) {
        int arr[] = {1,2,5,7,11,14,16,17,20,22,25,28,30,33,35,38,40,42,46,50,53,57,58,60, 55, 44, 33, 22, 11 , 10, 7, 6, 5, 1};

        int index= trisection_search(arr);
        if (index == -1){
            System.out.println("找不到");
        }else{
            System.out.println("索引" + index + "值为" + arr[index]);
        }
    }
}

当然,三分查找也可以再单调函数中找最值

static bool Find(int[] sortedArray, int number)
{
 if (sortedArray.Length == 0)
  	return false;
  	
 int start = 0;
 int end = sortedArray.Length - 1;
 while (end >= start)
 {
	  int firstMiddle = (end - start) / 3 + start;
	  int secondMiddle = end - (end - start) / 3;
	  if (sortedArray[firstMiddle] > number)
	  		end = firstMiddle - 1;
	  else if (sortedArray[secondMiddle] < number)
	  	    start = secondMiddle + 1;
	  else if (sortedArray[firstMiddle] != number && sortedArray[secondMiddle] != number)
	  {
		   end = secondMiddle - 1;
		   start = firstMiddle + 1;
	  }
	  else
	   	return true;
 }
 return false;
}

在这里插入图片描述

### 三分搜索算法的时间复杂度分析 三分搜索是一种基于分治策略的优化方法,通常用于单峰函数(即先单调递增后单调递减)或者类似的场景下寻找最优解。其核心思想是通过将区间分为三部分来逐步缩小搜索范围。 #### 基本原理 在三分搜索中,每次迭代会选取区间的两个分割点 \( \text{mid}_1 \) 和 \( \text{mid}_2 \),分别位于当前区间的三分之一和三分之二位置上。通过对这两个点处的目标函数值进行比较,可以判断目标值所在的子区间并舍弃其余部分。这一过程类似于二分搜索,但涉及三个分区而非两个[^1]。 #### 时间复杂度推导 假设初始区间长度为 \( n \),则每一次操作都会使剩余区间的大小缩减至原来的约 \( \frac{n}{3} \) 或更少。因此,在最坏情况下经过若干轮之后当剩下仅有一个候选单元时停止计算: - **层数估算** 对于每一轮迭代来说,新区间长度变为原长除以三的结果。如果总共进行了 \( k \) 轮这样的划分直至最后剩下一个单位长度,则满足如下不等式关系:\[ (\frac{1}{3})^{k}\cdot n\leqslant 1 \] 由此可得关于步数\( k \):\[ k=\lceil log_{3}(n)\rceil \] 由于每一层都需要做两次评估(针对两个内部测试点),故总体运算成本大致等于这些层次乘以其固定开销——也就是两倍于上述对数值形式表达出来的高度数量级上的常系数项相加起来形成最终渐近界表示法下的结论: 总时间复杂度为 O(2 * log₃(n)) ≈ O(log₃(n))[^1]. ```python def ternary_search(f, left, right, epsilon=1e-7): while (right - left) > epsilon: third = (right - left) / 3 mid1 = left + third mid2 = right - third if f(mid1) < f(mid2): left = mid1 else: right = mid2 return (left + right) / 2 ``` 此代码片段展示了如何实现基本版本的连续域内的三分查找功能。它接受一个定义良好且具有单一峰值特性的实值函数f以及边界[left,right],并通过不断调整左右端点的位置逼近极大值所在的具体坐标位置。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值