二分法及对数器

这篇博客详细介绍了二分查找算法在有序数组中查找目标值、找到大于等于目标值的最左侧位置以及在无序数组中寻找局部最小值的应用。针对每种情况,提供了具体的Java实现,并通过比较优化的二分查找方法与简单的遍历方法来确保算法的正确性。此外,还提出了对数器的概念,用于在大量随机数据上验证算法的正确性,确保在没有测试环境的情况下也能进行有效的测试和调试。
摘要由CSDN通过智能技术生成

二分法:

区分两种写法:

(一):target在闭区间【left,right】中时:right初始值取(len - 1),

当(target < arr[mid])时,right = mid - 1.(因为闭区间一定取不到mid)

(二):target在左闭右开【left,right)中时:right初始值取len,

当(target < arr[mid])时,right = mid .(因为闭区间可能取不到mid)

题目一:在arr中找出目标值的位置

 /**
     * 二分查找,查找有序数组中是否包含给出的数,如果有返回该位置,没有返回-1
     * 停止条件:如果找到,找到该数停止
     */
    public static int binarySearch(int[] arr, int target){
        if (arr == null) return -1;
        int left = 0;
        int right = arr.length ; //如果target在左闭右开区间,right初始值应该不能-1
        while (left < right){
            int mid = left + ((right - left) >> 1);
            if(arr[mid] > target){
                right = mid;
            }else if(arr[mid] < target){
                left = mid + 1;
            }else{
                return mid;
            }
        }
        return -1;
    }

题目二:在arr中找出大于等于目标值的最左侧位置

 /**
     * 二分查找,查找有序数组中 >= 给出数的最左侧的位置(找左边界)
     * 停止条件:一直二分到最后,范围内没有数了
     */
    public  static int leftest(int[] arr, int target){
        if(arr == null || arr.length < 1) return -1;
        int left = 0;
        int right = arr.length - 1; //如果target的在左闭右闭区间,right初始值应该是能取到的len-1
        int leftest = -1;
        while(left <= right){
            int mid = left + ((right - left) >> 1);
            if(target > arr[mid]){
                left = mid + 1;
            }else{
                leftest = mid; //!!!!这两句不能调换,如果调换了寻找的区域是不包含target的边界值
                right = mid - 1;//!!!
            }
        }
        return leftest;
    }

题目三:找出无序数组中的局部最小值(任意两个相邻的数不相等)

 /**
     * 在无序数组中,任何两个数不相等,使用二分法找出任意一个局部最小
     * 1. 0位置 < 1位置(返回0)
     * 2. N-2位置 > N-1位置(返回N-1)
     * 3. i-1位置 < i位置 < i+1位置(返回i)
     */
    public static int searchMin(int[] arr){
        if(arr == null || arr.length == 0) return -1;
        int len = arr.length;
        if(arr.length == 1 || arr[0] < arr[1]) return 0;
        if(arr[len - 2] > arr[len - 1]) return len - 1;
        int left = 0;
        int right = len - 1;
        while(left <= right){//开始的时候,除去第一步返回1位置的情况外,状态一定是左右两边向中间聚拢的趋势
            int mid = left + ((right - left) >> 1);
            //mid=0只有在left和right都为0或着left=0,right=1时。返回0时的情况已经被第二个if判断了,因此只有返回1的情况了
            if(mid == 0) return mid + 1;
            //如果mid的位置刚好是局部最小,直接返回
            if(arr[mid - 1] > arr[mid] && arr[mid] < arr[mid + 1]){
                return mid;
            }
            //如果左半部分包含局部最小,不需要包含left和left+1的关系,因为在之前已经判断过了(之前可能作为mid或者一开始的判断)
            else if(arr[mid] > arr[mid - 1]){
                right = mid - 1;
            }
            //如果右半部分包含局部最小+左半部分和右半部分都包含局部最小(两边都可以找)
            else {
                left = mid + 1;
            }
        }
        return -1;
    }

优化流程:特殊的数据状况、特殊的问题求解 

对数器:

使用场景:在没有测试环境的情况下,编写的测试自己写的方法是否在正确一种方式

具体方法:将自己写的方法和一个简单正确的方法同时跑大量的不同的数据(先跑小样本试试),如果的到的结果一致,则说明自己的方法正确;如果不一致,则可能是自己的方法出现了问题,也可能是对比方法出现了问题,这时使用较小的数据调试,之后在进行测试。

使用Math.random获取 A,B 闭区间之间的数 ——> (int) (Math.random()*(B-A+1)+A)

以排序为例:

 public static void main(String[] args) {
        int[] arr = new int[]{3,2,1,1,3,6,9,7};
        //使用对数器验证方法的正确性
        boolean success = true;
        for(int i = 0; i < 50000; i++){
            int[] arr1 = getRandomArr(20, 100);
            int[] arr2 = copyArray(arr1);
            selectSort(arr1);//作为测试算法
            bubbleSort(arr2);//作为对比算法
            if(!isEqual(arr1, arr2)){
               success = false;
               break;
            }
        }
        System.out.println(success ? "success" : "fail");
    }

 /**
     * 对数器生成一个随机长度的内容随机的数组
     * 对数器就是为了验证自己的方法是否正确,将自己的方法和简单正确的方法同时运行大量的相同的随机数据,看结果是否相同
     * 如果相同则此方法正确,如果不相同则有错误,然后缩小数据范围调试,在运行
     */
    public static int[] getRandomArr(int maxSize, int maxValue){
        int[] arr = new int[(int)Math.random()*(maxSize+1)];
        for(int i = 0; i < arr.length; i++){
            arr[i] = ((int)Math.random()*(maxValue+1)) - ((int)Math.random()*(maxValue+1));
        }
        return arr;
    }

    /**
     * 复制数组,重新开辟空间
     * @param org
     * @return
     */
    public static int[] copyArray(int[] org){
        int[] arr = new int[org.length];
        for(int i = 0; i < org.length; i++){
            arr[i] = org[i];
        }
        return arr;
    }

    /**
     * 比较两个数组的元素是否相同
     * @param arr1
     * @param arr2
     * @return
     */
    public static boolean isEqual(int[] arr1, int[] arr2){
        if(arr1 == null || arr2 == null || arr1.length != arr2.length){
            return false;
        }
        for(int i = 0; i < arr1.length; i++){
            if(arr1[i] != arr2[i]) return false;
        }
        return true;
    }

本来想使用对数器验证局部最小值的 ,但对比方法的结果不能保证与实现方法一定一致。

/**
     * 对数器
     */

    /**
     * 生成随机长度(在给定的最大长度范围内)的随机内容(在给定最大值范围内)数组
     * Math.random()——>[0,1)
     * (int)是向下取整
     *使用random获取 A,B 闭区间之间的数 ——> (int) (Math.random()*(B-A+1)+A)
     * @param maxSize: 长度范围[0,maxSize]
     * @param maxValue: 数据范围[-maxValue,+maxValue]
     * @return
     */
    public static int[] getRandomArr(int maxSize, int maxValue){
        int[] arr = new int[(int)Math.random()*(maxSize+1)];//数组长度在[0,maxSize]
        for(int i = 0; i < arr.length; i++){
            arr[i] = (int)(Math.random()*(maxValue+1)) - (int)(Math.random()*(maxValue+1));//数组元素在[0,maxValue-1]
        }
        return arr;
    }

    /**
     * 为了测试对数器写的简单、复杂度高、保证准确的方法
     * 寻找局部最小值的普通方法(但是由于二分法和从头遍历找到的第一个局部最小值可能不是一个。。。)
     * @return
     */
    public static int ordinaryMethod(int[] arr){
        int len = arr.length;
        if(arr == null || arr.length == 0) return -1;
        if(arr.length == 1) return 0;
        if(arr[0] < arr[1]) return 0;
        if(arr[0] > arr[1]) return 1;
        if(arr[len-2] > arr[len - 1]) return len - 1;
        for(int i = 1; i < len - 1; i++){
            if(arr[i] < arr[i-1] && arr[i] < arr[i+1]) return i;
        }
        return -1;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值