Java数据结构与算法——二分查找与插值查找

本文深入探讨了二分查找和插值查找算法,详细解释了两种算法的工作原理、适用场景及其优缺点。通过对比,揭示了插值查找如何在均匀分布的数据集上优化二分查找的性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 概述

常见的查找算法有顺序查找、二分查找、插值查找、黄金查找。二分法查找是基于有序的基础上进行的,通过比较序列的中间值来折半缩小查找的区间,从而实现快速的查找,虽然二分法查找已经到了一个较快的水平但是当我们要的目标元素是位于序列的两端时,仍然还是会产生一些额外的折半开销,因为二分查找在一定的局部空间内是从中间开始的,所以位于两端的元素的查找就会有一个较多的折半次数,才可以定位到目标元素。插值查找通过一个均值算法解决了二分法查找的这个缺点,但是插值查找是在序列元素有分布较为均匀的时候才有一个更好的表现,如果序列中的元素分布不均匀那么插值查找的算法未必优于二分查找。总的来说二分查找与插值查找的实现思路是相同的。

  • 二分查找

 实现的思路在代码中已经有了详细的解释,可以直接阅读代码(查找序列中所有的目标值索引、查找序列中目标值的一个索引):

public class BinarySearch {

    public static void main(String[] args) {
        //测试数据与程序
        int arr[] ={11,33,44,55,66,66,66,66,66,66,66,66,896,3444};
        List<Integer> resIndexList = binarySearch2(arr,0,arr.length-1,66);
        System.out.println("元素所在的索引为:  "+resIndexList.toString());
    }

    public static int binarySearch(int[] arr,int left,int right,int findVal){
        /**
         * description:查找到一个目标元素值就放回
         * function:
          * @Param: arr数组
         * @Param: left左边索引
         * @Param: right右边索引
         * @Param: findVal需要查找的值
         * @return int
         */
        //当left>right说明没有找到数据,这是跳出递归的条件
        if(left>right){
            return -1;
        }
        //在这个过程中,mid = (left+right)/2、以及下面递归中的mid+1或mid-1可以看成是递归条件
        // 的一个变换,通过这些递归条件的不断变换,当达到left>right时结束递归逐层返回-1,表明
        // 目标元素不存在
        int mid = (left+right)/2;
        int midVal = arr[mid];

        //目标元素大于中间元素,说明目标元素在中间元素的右边,对右半部分的元素进行递归二分查找
        if(findVal>midVal){
            return binarySearch(arr,mid+1,right,findVal);
        }else if(findVal<midVal){
        //目标元素小于中间的元素,说明目标元素在中间元素的左边,对左半部分的元素进行递归二分查找
            return binarySearch(arr,left,mid-1,findVal);
        }else {
        //目标元素等于中间的元素,即目标元素找到
            return mid;
        }
    }

    public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal){
        /**
         * description:查找到目标元素所有的索引值
         * function:
         * @Param: arr数组
         * @Param: left左边索引
         * @Param: right右边索引
         * @Param: findVal需要查找的值
         * @return int
         */
        //当left>right说明没有找到数据
        if(left>right){
            return new ArrayList<Integer>();
        }
        int mid = (left+right)/2;
        int midVal = arr[mid];

        if(findVal>midVal){
            return binarySearch2(arr,mid+1,right,findVal);
        }else if(findVal<midVal){
            return binarySearch2(arr,left,mid-1,findVal);
        }else {
            //是查找目标元素的一个下标还是目标元素的所有下标,处理上的差别实在findVal=midVal的这个
            //循环体中,我们不知道到底有多少个目标元素,所以选择可以自动扩展的集合来进行接收,又因为
            //我们对这个集合的操作主要是顺序插入,所以比较ArrayList与LinkedList这里的集合类型选用
            //底层采用动态数组实现的ArrayList
            ArrayList<Integer> resIndexList = new ArrayList<>();
            //因为序列是有序的所以如果序列中存在多个目标值的话,这些目标值也必定是相连的,所以查找是否
            //还有更多的目标元素就是看一下第一个找到的目标元素的位置左右两边是否还有目标元素
            //temp是一个用来遍历左右两边是否还有更多目标元素的变量,temp = mid-1表示看左边
            int temp = mid-1;
            while(true){
                if(temp<0||arr[temp]!=findVal){
                    break;
                }
                resIndexList.add(temp);
                temp -=1;
            }
            //这句话放置在两个循环体的前中后均可
            resIndexList.add(mid);
            //temp = mid+1表示向右边看
            temp = mid+1;
            while (true){
                if (temp > arr.length - 1 ||arr[temp]!=findVal) {
                    break;
                }
                resIndexList.add(temp);
                temp +=1;
            }
            //对放回的结果进行一个排序
            Collections.sort(resIndexList);
            return resIndexList;
        }
    }

}
  • 二分查找的非递归实现

public class SearchNoRecursion {

    public static void main(String[] args) {
        int arrays[] ={1,3,8,90,234,567,890,1111,4566};
        searchNoRecur(arrays,8950);
    }

    public static void searchNoRecur(int[] arr,int targetValue){

        int left = 0;
        int right = arr.length-1;
        int middle = (left+right)/2;

        while (left<=right){
            if(arr[middle]==targetValue){
                System.out.println("Searching successfully , index is "+middle);
                return;
            }else if(arr[middle]<targetValue){
                left = middle+1;
                middle = (left+right)/2;
            }else if(arr[middle]>targetValue){
                right = middle-1;
                middle = (left+right)/2;
            }
        }

        System.out.println("Sorry,targetValue is not exist in targetArrays");

    }

}
  • 插值查找

插值查找与二分查找的区别主要是mid的计算:

二分查找中mid=(left+right)/2;

插值查找:mid = left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);

差值查找公式中的那个长的分子分母公式我们可以将它看作是一个控制查找位置的游标,前面的left就是称杆的起始点,(right-left)是秤杆的长度,个长的分子分母公式就是我们的游标。

 代码实现,代码的思路类比于二分查找,同时在代码中通过注释详细的介绍了实现的思路,可以直接阅读代码(递归实现,非递归实现):

public class InsertValueSearch {

    public static void main(String[] args) {
        //测试数据
        int arr[] =new int[100];
        for (int i = 0; i <100 ; i++) {
            arr[i] = i+1;
        }
        System.out.println("24的索引为"+insertValueSearch2(arr,0,arr.length-1,24));
    }

    public static int insertValueSearch(int[] arr,int left,int right,int findVal){
        /**
         * description:递归实现查找一个目标值索引
         * function:
          * @Param: arr数组
         * @Param: left左边的索引
         * @Param: right右边的索引
         * @Param: findVal查找的值
         * @return int所找值的下标索引
         */
        //findVal<arr[0]||findVal>arr[arr.length-1]是必须需要的,否则可能越界,当
        // findVal是非常大的,同时又是不存在与序列中的时候会发生越界
        if(left>right||findVal<arr[0]||findVal>arr[arr.length-1]){
            return -1;
        }
        //求出mid,这一块是与二分法查找的核心区别所在
        int mid = left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
        int midVal = arr[mid];
        if(findVal>midVal){
            return insertValueSearch(arr,mid+1,right,findVal);
        }else if(findVal<midVal){
            return insertValueSearch(arr,left,mid-1,findVal);
        }else {
            //如果要返回目标值的所有索引则可以参考二分法中的实现
            return mid;
        }
    }

    public static ArrayList<Integer> insertValueSearch2(int[] arr, int left, int right, int findVal){
        /**
         * description:非递归实现查找多个目标值索引
         * function:
         * @Param: arr数组
         * @Param: left左边的索引
         * @Param: right右边的索引
         * @Param: findVal查找的值
         * @return int所找值的下标索引
         */
        //这个用于接收的数组必须放置在方法域中,如果放置在判断体中进行定义放回的话,会因为有判断体
        //没有返回值而报错
        ArrayList<Integer> resIndexList = new ArrayList<>();
        //这里与用递归实现的一个区别是递归中的判断条件判断的是不成立的,而在这里for循环中判断的是成立
        //的条件,同时条件变量应该与方法体参数的区分开来,不然的话会出现死循环;条件变量值的改变不是在
        //for()后的括号中而应该是在for的循环体的判断分支语句中,因为条件变量每次的变话根据不同的情况时
        //有不同的变化的
        for (int left2 = 0,right2=arr.length-1; left2 <=right2&&findVal>=arr[0]&&findVal<=arr[arr.length-1];) {
            int mid = left2+(right2-left2)*(findVal-arr[left2])/(arr[right2]-arr[left2]);
            int midVal = arr[mid];

            if(findVal>midVal){
                left2=mid+1;
                right2=right2;
            }else if(findVal<midVal){
                System.out.println("left---->left:"+left2+"  right"+right2);
                left2=left2;
                right2=mid-1;
            }else {
                int temp = mid-1;
                while(true){
                    if(temp<0||arr[temp]!=findVal){
                        break;
                    }
                    resIndexList.add(temp);
                    temp -=1;
                }
                //这句话放置在两个循环体的前中后均可
                resIndexList.add(mid);
                //temp = mid+1表示向右边看
                temp = mid+1;
                while (true){
                    if (temp > arr.length - 1 ||arr[temp]!=findVal) {
                        break;
                    }
                    resIndexList.add(temp);
                    temp +=1;
                }
                //对放回的结果进行一个排序
                Collections.sort(resIndexList);
                break;
            }
        }
        return resIndexList;
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值