二分查找算法&最靠左索引&最靠右索引详解与优化:图文全解+代码详注+思路分析(二)

简介: 二分查找算法&最靠左索引&最靠右索引详解与优化:图文全解+代码详注+思路分析

8.Java源码中二分查找的使用

8.1 Arrays.binarySearch(int[] a, int key)

/**
     * 使用二进制搜索算法在指定的整数数组中搜索指定的值。在进行此调用之前,必须对数组进行排序(按方法排序 sort(int[]) )。
     * 如果未排序,则结果未定义。如果数组包含多个具有指定值的元素,则无法保证会找到哪个元素。
     * 参数:
   *    a – 要搜索的数组 
   *    key – 要搜索的值
   * 返回:搜索键的索引(如果它包含在数组中);否则,( -(插入点)-1)。
   * 插入点定义为将键插入数组的 点 :第一个元素的索引大于键,如果数组中的所有元素都小于指定的键,则为 a.length 。
   * 请注意,这保证了当且仅当找到键时返回值将为 >= 0。
     */
    public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
    }
    // Like public version, but without range checks.
    private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                     int key) {
        int low = fromIndex;
        int high = toIndex - 1;
        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];
            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }

8.2 实现二分查找目标值,不存在则插入

public static void main(String[] args) {
        // 二分查找目标值,不存在则插入
        /*
            原始数组:[2,5,8]
            查找目标值:4
            查询不到,返回的结果为 r = -待插入点索引-1
            在这里带插入点索引为 1,对应 r = -2
            那么我们分成这几步来进行拷贝:
                - 1.新建数组,大小为原数组的大小+1:         [0,0,0,0]
                - 2.将待插入点索引之前的数据放入新数组:     [2,0,0,0]
                - 3.将目标值放入到待插入点索引的位置:       [2,4,0,0]
                - 4.将原数组后面的数据都相继拷贝到新数组后面: [2,4,5,8]
         */
        // 定义原数组与目标值
        int[] oldArray = {2, 5, 8};
        int target = 4;
        // 搜索目标值4,没有找到,返回结果为 r =  -待插入点索引-1,这里的 r=-2
        int r = Arrays.binarySearch(oldArray, target);
        // r < 0 说明没有找到目标值,就插入
        if (r < 0) {
            // 获取待插入索引
            int insertIndex = -r - 1;
            // 1.新建数组,大小为原数组的大小+1
            int[] newArray = new int[oldArray.length + 1];
            // 2.将待插入点索引之前的数据放入新数组
            System.arraycopy(oldArray, 0, newArray, 0, insertIndex);
            // 3.将目标值放入到待插入点索引的位置
            newArray[insertIndex] = target;
            // 4.将原数组后面的数据都相继拷贝到新数组后面
            System.arraycopy(oldArray, insertIndex, newArray, insertIndex + 1, oldArray.length - insertIndex);
            System.out.println(Arrays.toString(newArray));
        }
    }

9.LeftRightmost

9.1 最靠左索引

搜索目标值为 target 且 索引最小的索引位置

public static int binarySearchLeftmost(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        // 记录最小索引
        int minIndex = -1;
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target < array[mid]) {
                // 目标值小于中间索引值,缩小右范围
                right = mid - 1;
            } else if (array[mid] < target) {
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            } else {
                minIndex = mid;
                // 由于要查找最小索引,因此缩小右范围即可
                right = mid - 1;
            }
        }
        // 返回结果
        return minIndex;
    }

9.2 最靠右索引

搜索目标值为 target 且 索引最大的索引位置

public static int binarySearchRightmost(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        // 记录最大索引
        int maxIndex = -1;
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target < array[mid]) {
                // 目标值小于中间索引值,缩小右范围
                right = mid - 1;
            } else if (array[mid] < target) {
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            } else {
                maxIndex = mid;
                // 由于要查找最大索引,因此缩小左范围即可
                left = mid + 1;
            }
        }
        // 返回结果
        return maxIndex;
    }

9.3 返回≥目标的最靠左索引

搜索大于等于目标值的最小索引位置

🍀 普通代码

public static int binarySearchLeftmostFirst(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        // 记录最小索引
        int minIndex = -1;
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target < array[mid]) {
                // array[mid] 满足大于目标值,因此可以记录
                minIndex = mid;
                // 目标值小于中间索引值,缩小右范围
                right = mid - 1;
            } else if (array[mid] < target) {
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            } else {
                // array[mid] 满足等于目标值,因此可以记录
                minIndex = mid;
                // 由于要查找最小索引,因此缩小右范围即可
                right = mid - 1;
            }
        }
        // 返回结果,如果返回为-1说明没有找到,即数组所有元素都比目标值要小
        return minIndex;
    }

🍀 第一次优化

可以看到 while 循环中的 if 和 else if 中的语句相同,因此可以做一次合并。

在 if 语句中,我们的操作是:

  1. 找到了 mid 索引,满足 array[mid] 大于等于目标值,
  2. 用 minIndex 记录这个大于等于目标值的索引,
  3. 然后将 mid -1 赋值给 right,也就是 (right + 1) 为当前找到的大于等于目标值的最小索引

if 语句结束,就可以继续向前搜索看是否比当前 minIndex 更小的大于等于目标值的索引。

在本次优化中,我们保证了:

  • 除非目标值大于数组元素的最大值,
  • 否则最终循环结束时的 (right + 1) 指针指向的一定是大于等于目标值的最小索引,
  • 也就是 minIndex = right + 1 。
public static int binarySearchLeftmostSecond(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        // 记录最小索引
        int minIndex = -1;
        /*
            在 if 语句中,我们的操作是:
                1. 找到了 mid 索引,满足 array[mid] 大于等于目标值,
                2. 用 minIndex 记录这个大于等于目标值的索引,
                3. 然后将 mid -1 赋值给 right,也就是 (right + 1) 为当前找到的大于等于目标值的最小索引。
            if语句结束,就可以继续向前搜索看是否比当前 minIndex 更小的大于等于目标值的索引。
            在本次优化中,我们保证了:
                除非目标值大于数组元素的最大值,
                否则最终循环结束时的 (right + 1) 指针指向的一定是大于等于目标值的最小索引,
                也就是 minIndex = right + 1 。
            这个结论是后面的第二次优化的核心!!!
         */
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target <= array[mid]) {
                // array[mid] 满足大于等于目标值,因此可以记录
                minIndex = mid;
                // 目标值小于中间索引值,缩小右范围
                right = mid - 1;
            } else if (array[mid] < target) {
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            }
        }
        // 返回结果
        return minIndex;
    }

🍀 第二次优化

在第一次优化中,我们保证了:

  • 除非目标值大于数组元素的最大值,
  • 否则最终循环结束时的 (right + 1) 指针指向的一定是大于等于目标值的最小索引,
  • 也就是最终 minIndex = right + 1 。

我们再肯定一件事情:最终退出while循环的情况一定是 left > right 且 right + 1 = left 。

而造成 left 比 right 多1,即 left 在 right 指针后面一位,一定是因为:

  • 在以 left == right 为满足循环条件时,执行了 if 语句中right左移font> 或者 else if语句中的left右移。
  • 我们之前又得到结论:最终循环结束时的 (right + 1) 指针指向的一定是大于等于目标值的最小索引。
  • 当执行的是 if语句中的right左移后,导致了新(right + 1)的值就是left,则left就是大于等于目标值的最小索引。
  • 当执行的是 else if语句中的left右移后,导致了left移动到了当前(right+1)指针的位置,则left就是大于等于目标值的最小索引。

综上所述,我们发现:

  • 最终的left 指针就是那个大于等于目标值的最小索引,
  • 所以我们无需用 minIndex 进行记录,直接最终 return left 即可。
  • 那么这时候当目标值大于数组元素的最大值时,返回的left 就是 数组最大索引+1。
public static int binarySearchLeftmostThird(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        /*
            在第一次优化中,我们保证了:
                除非目标值大于数组元素的最大值,
                否则最终循环结束时的 (right + 1) 指针指向的一定是大于等于目标值的最小索引,
                也就是最终 minIndex = right + 1 。
            while (left <= right) {
                mid = (left + right) >>> 1;
                if (target <= array[mid]) {
                    minIndex = mid;
                    right = mid - 1;
                } else if (array[mid] < target) {
                    left = mid + 1;
                }
            }
            我们再肯定一件事情:
                最终退出while循环的情况一定是 left > right 且 right + 1 = left 。
            而造成 left 比 right 多1,即 left 在 right 指针后面一位,一定是因为
                在以 left == right 为满足循环条件时,
                执行了if 语句中right左移 或者 else if语句中的left右移。
                我们之前又得到结论:最终循环结束时的 (right + 1) 指针指向的一定是大于等于目标值的最小索引。
                    - 当执行的是if语句中的right左移后,导致了新(right + 1)的值就是left,则left就是大于等于目标值的最小索引。
                    - 当执行的是else if语句中的left右移后,导致了left移动到了当前(right+1)指针的位置,则left就是大于等于目标值的最小索引。
             综上所述,我们发现:最终的left 指针就是那个大于等于目标值的最小索引,
             所以我们无需用 minIndex 进行记录,直接最终 return left 即可。
             那么这时候当目标值大于数组元素的最大值时,返回的left 就是 数组最大索引+1。
         */
        // 不需记录最小索引
        // int minIndex = -1;
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target <= array[mid]) {
                // array[mid] 满足大于等于目标值,因此可以记录
                // minIndex = mid;
                // 目标值小于中间索引值,缩小右范围
                right = mid - 1;
            } else if (array[mid] < target) {
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            }
        }
        // 返回结果
        return left;
    }

9.4 返回≤目标的最靠右索引

搜索小于等于目标值的最大索引位置

🍀 普通代码

public static int binarySearchRightmostFirst(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        // 记录最小索引
        int minIndex = -1;
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target < array[mid]) {
                // 目标值小于中间索引值,缩小右范围
                right = mid - 1;
            } else if (array[mid] < target) {
                // array[mid] 满足小于目标值,因此可以记录
                minIndex = mid;
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            } else {
                // array[mid] 满足等于目标值,因此可以记录
                minIndex = mid;
                // 由于要查找最大索引,因此缩小左范围即可
                left = mid - 1;
            }
        }

🍀 第一次优化

public static int binarySearchRightmostSecond(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        // 记录最大索引
        int maxIndex = -1;
        /*
            在 else if 语句中,我们的操作是:
                1. 找到了 mid 索引,满足 array[mid] 小于等于目标值,
                2. 用 maxIndex 记录这个小于等于目标值的索引,
                3. 然后将 mid + 1 赋值给 left,也就是 (left - 1) 为当前找到的小于等于目标值的最大索引。
            else if语句结束,就可以继续向后搜索看是否比当前 maxIndex 更大的小于等于目标值的索引。
            在本次优化中,我们保证了:
                除非目标值小于数组元素的最小值,
                否则最终循环结束时的 (left - 1) 指针指向的一定是小于等于目标值的最大索引,
                也就是 maxIndex = left - 1 。
            这个结论是后面的第二次优化的核心!!!
         */
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target < array[mid]) {
                // 目标值小于中间索引值,缩小右范围
                right = mid - 1;
            } else if (array[mid] <= target) {
                // array[mid] 满足小于等于目标值,因此可以记录
                maxIndex = mid;
                // 目标值大于中间索引值,缩小左范围
                left = mid + 1;
            }
        }
        // 返回结果
        return maxIndex;
    }

🍀 第二次优化

public static int binarySearchRightmostThird(int[] array, int target) {
        int left = 0;
        int right = array.length - 1;
        int mid;
        /*
            在第一次优化中,我们保证了:
                除非目标值小于数组元素的最小值,
                否则最终循环结束时的 (left - 1) 指针指向的一定是小于等于目标值的最大索引,
                也就是最终 maxIndex = left - 1 。
            while (left <= right) {
                mid = (left + right) >>> 1;
                if (target < array[mid]) {
                    right = mid - 1;
                } else if (array[mid] <= target) {
                    maxIndex = mid;
                    left = mid + 1;
                }
            }
            我们再肯定一件事情:
                最终退出while循环的情况一定是 left > right 且 right = left - 1。
            而造成 right 比 left 少1,即 right 在 left 指针前面一位,一定是因为
                在以 left == right 为满足循环条件时,
                执行了if 语句中right左移 或者 else if语句中的left右移。
                我们之前又得到结论:最终循环结束时的 (left - 1) 指针指向的一定是小于等于目标值的最大索引。
                    - 当执行的是if语句中的right左移后,导致了right移动到了当前(left - 1)的指针位置,则right就是小于等于目标值的最大索引。
                    - 当执行的是else if语句中的left右移后,导致了新(left-1)指针的位置就是当前right的位置,则right就是小于等于目标值的最大索引。
             综上所述,我们发现:最终的 right 指针就是那个小于等于目标值的最大索引,
             所以我们无需用 maxIndex 进行记录,直接最终 return right 即可。
             那么这时候当目标值小于数组元素的最小值时,返回的 right 就是 -1。
         */
        // 不需记录最小索引
        // int maxIndex = -1;
        while (left <= right) {
            mid = (left + right) >>> 1;
            if (target < array[mid]) {
                right = mid - 1;
            } else if (array[mid] <= target) {
//                maxIndex = mid;
                left = mid + 1;
            }
        }
        // 返回结果,当然这个结果也可以写成 return left - 1
        return right;
    }

9.5 实际应用

范围查询

image.png

求最近邻居

  • 前任和后任距离更近者
目录
打赏
0
0
0
0
14
分享
相关文章
基于和声搜索优化算法的机器工作调度matlab仿真,输出甘特图
本程序基于和声搜索优化算法(Harmony Search, HS),实现机器工作调度的MATLAB仿真,输出甘特图展示调度结果。算法通过模拟音乐家即兴演奏寻找最佳和声的过程,优化任务在不同机器上的执行顺序,以最小化完成时间和最大化资源利用率为目标。程序适用于MATLAB 2022A版本,运行后无水印。核心参数包括和声记忆大小(HMS)等,适应度函数用于建模优化目标。附带完整代码与运行结果展示。
Windows共享文件:探秘C++实现的B树索引算法奇境
在数字化时代,Windows共享文件的高效管理至关重要。B树算法以其自平衡多路搜索特性,在文件索引与存储优化中表现出色。本文探讨B树在Windows共享文件中的应用,通过C++实现具体代码,展示其构建文件索引、优化数据存储的能力,提升文件检索效率。B树通过减少磁盘I/O操作,确保查询高效,为企业和个人提供流畅的文件共享体验。
18个常用的强化学习算法整理:从基础方法到高级模型的理论技术与代码实现
本文系统讲解从基本强化学习方法到高级技术(如PPO、A3C、PlaNet等)的实现原理与编码过程,旨在通过理论结合代码的方式,构建对强化学习算法的全面理解。
63 10
18个常用的强化学习算法整理:从基础方法到高级模型的理论技术与代码实现
基于GA遗传优化TCN-GRU时间卷积神经网络时间序列预测算法matlab仿真
本项目基于MATLAB2022a开发,提供无水印算法运行效果预览及核心程序(含详细中文注释与操作视频)。通过结合时间卷积神经网络(TCN)和遗传算法(GA),实现复杂非线性时间序列的高精度预测。TCN利用因果卷积层与残差连接提取时间特征,GA优化超参数(如卷积核大小、层数等),显著提升模型性能。项目涵盖理论概述、程序代码及完整实现流程,适用于金融、气象、工业等领域的时间序列预测任务。
|
16天前
|
员工行为监控软件中的 Go 语言哈希表算法:理论、实现与分析
当代企业管理体系中,员工行为监控软件已逐步成为维护企业信息安全、提升工作效能的关键工具。这类软件能够实时记录员工操作行为,为企业管理者提供数据驱动的决策依据。其核心支撑技术在于数据结构与算法的精妙运用。本文聚焦于 Go 语言中的哈希表算法,深入探究其在员工行为监控软件中的应用逻辑与实现机制。
55 14
基于遗传优化算法的多AGV栅格地图路径规划matlab仿真
本程序基于遗传优化算法实现多AGV栅格地图路径规划的MATLAB仿真(测试版本:MATLAB2022A)。支持单个及多个AGV路径规划,输出路径结果与收敛曲线。核心程序代码完整,无水印。算法适用于现代工业与物流场景,通过模拟自然进化机制(选择、交叉、变异)解决复杂环境下的路径优化问题,有效提升效率并避免碰撞。适合学习研究多AGV系统路径规划技术。
基于GA遗传优化TCN时间卷积神经网络时间序列预测算法matlab仿真
本内容介绍了一种基于遗传算法优化的时间卷积神经网络(TCN)用于时间序列预测的方法。算法运行于 Matlab2022a,完整程序无水印,附带核心代码、中文注释及操作视频。TCN通过因果卷积层与残差连接学习时间序列复杂特征,但其性能依赖超参数设置。遗传算法通过对种群迭代优化,确定最佳超参数组合,提升预测精度。此方法适用于金融、气象等领域,实现更准确可靠的未来趋势预测。
基于NSGAII的的柔性作业调度优化算法MATLAB仿真,仿真输出甘特图
本程序基于NSGA-II算法实现柔性作业调度优化,适用于多目标优化场景(如最小化完工时间、延期、机器负载及能耗)。核心代码完成任务分配与甘特图绘制,支持MATLAB 2022A运行。算法通过初始化种群、遗传操作和选择策略迭代优化调度方案,最终输出包含完工时间、延期、机器负载和能耗等关键指标的可视化结果,为制造业生产计划提供科学依据。
基于GA遗传优化TCN-LSTM时间卷积神经网络时间序列预测算法matlab仿真
本项目基于MATLAB 2022a实现了一种结合遗传算法(GA)优化的时间卷积神经网络(TCN)时间序列预测算法。通过GA全局搜索能力优化TCN超参数(如卷积核大小、层数等),显著提升模型性能,优于传统GA遗传优化TCN方法。项目提供完整代码(含详细中文注释)及操作视频,运行后无水印效果预览。 核心内容包括:1) 时间序列预测理论概述;2) TCN结构(因果卷积层与残差连接);3) GA优化流程(染色体编码、适应度评估等)。最终模型在金融、气象等领域具备广泛应用价值,可实现更精准可靠的预测结果。
基于WOA鲸鱼优化的CNN-LSTM-SAM网络时间序列回归预测算法matlab仿真
本内容介绍了一种基于CNN-LSTM-SAM网络与鲸鱼优化算法(WOA)的时间序列预测方法。算法运行于Matlab2022a,完整程序无水印并附带中文注释及操作视频。核心流程包括数据归一化、种群初始化、适应度计算及参数更新,最终输出最优网络参数完成预测。CNN层提取局部特征,LSTM层捕捉长期依赖关系,自注意力机制聚焦全局特性,全连接层整合特征输出结果,适用于复杂非线性时间序列预测任务。