常用的查找算法(java实现)

java中常用的查找算法有4种:

  1. 顺序(线性)查找
  2. 二分查找
  3. 插值查找
  4. 斐波那契查找

一、顺序(线性)查找

顺序(线性)查找只需要数组的顺序依次进行查找,找到后返回数组的下标即可。顺序查找适用于任何数据。缺点是当查找越后面的数花费的时间越长。

二、二分查找

二分查找的前提条件是这个数组是有序的。
二分查找思路:

  1. 确定数组中间的下标与值。
  2. 将要查找的数与中间的数进行比较:若比中间的数小,则向比中间值大的区间查找,若比中间数小,则向比中间值小的区间查找,重复第二步,知道找到该数。
    代码实现:
public int binarySearch(int[] arr, int left, int right, int searchValue){
    if (left > right){ // 没有查找到
        return -1;
    }
    int mid = (left + right) / 2; // 中间值索引
    int midValue = arr[mid]; // 中间值
    if (searchValue < midValue){ // 左递归
        return binarySearch(arr, left, mid - 1, searchValue);
    }else if (searchValue > midValue){ // 右递归
        return binarySearch(arr, mid + 1, right, searchValue);
    }else {
        return mid;
    }
}

优化:当我们查找的数在该数组中有多个时,上面的方法只能返回一个值。优化返回相同的数的下标。利用集合存储查找到相同数的下标。
当找到一个数时,继续向左右寻找相同的数,并保持下标。

public ArrayList<Integer> binarySearch2(int[] arr, int left, int right, int searchValue){
    if (left > right){ // 没有查找到
        return new ArrayList<Integer>();
    }
    int mid = (left + right) / 2; // 中间值索引
    int midValue = arr[mid]; // 中间值
    if (searchValue < midValue){ // 左递归
        return binarySearch2(arr, left, mid - 1, searchValue);
    }else if (searchValue > midValue){ // 右递归
        return binarySearch2(arr, mid + 1, right, searchValue);
    }else {
        ArrayList<Integer> integers = new ArrayList<>();
        int temp = mid - 1;
        while (temp >= 0 && arr[temp] == arr[mid]){ //向左查找有无和查找值相同的数
            integers.add(temp); // 添加下标
            temp--;
        }
        integers.add(mid);
        temp = mid + 1;
        while (temp < arr.length && arr[temp] == arr[mid]){ // 向右查找有无和查找值相同的数
            integers.add(temp); // 添加下标
            temp++;
        }
        return integers;
    }
}

三、插值查找

插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid 处开始查找
原理:将二分查找中的求mid 索引的公式 , low 表示左边索引 left, high 表示右边索引 right. key 就是要查找的值。
在这里插入图片描述
其余分别与二分查找无异。
代码实现:

public class InsertSearch {

    public static void main(String[] args) {
        int[] arr = {1,5,90,500,1000,1000};
        InsertSearch insertSearch = new InsertSearch();
        int i = insertSearch.binarySearch(arr, 0, arr.length - 1, 500);
        System.out.println("i = " + i);
    }

    /**
     * @Description: binarySearch 插值查找
     * @param: [arr, left, right, searchValue]
     * @return: int
     * @auther: zqq
     */
    public int binarySearch(int[] arr, int left, int right, int searchValue){
        // 没有查找到, searchValue < arr[0] || searchValue > arr[arr.length - 1] 防止越界
        if (left > right || searchValue < arr[0] || searchValue > arr[arr.length - 1]){
            return -1;
        }
        int mid = left + (right - left) * (searchValue - arr[left]) / (arr[right] - arr[left]); // 中间值索引:插值查找
        int midValue = arr[mid]; // 中间值
        if (searchValue < midValue){ // 左递归
            return binarySearch(arr, left, mid - 1, searchValue);
        }else if (searchValue > midValue){ // 右递归
            return binarySearch(arr, mid + 1, right, searchValue);
        }else {
            return mid;
        }
    }
}

注意:

  1. 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快.
  2. 关键字分布不均匀的情况下,该方法不一定比二分查找要好

四、斐波那契查找

  • 黄金分割点:黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位 数字的近似值是 0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神 奇的数字,会带来意向不大的效果。
  • 斐波那契数列:{1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列第二个(包括第二个)数之后的两个相邻数 的比例,无限接近 黄金分割值 0.618。

斐波那契查找原理: 就是要查找到数组一个黄金分割点(mid):mid = left + F[k] - 1(mid为黄金分割点的下标)。若查找比黄金分割点的值小,则向比黄金分割点大的区域继续使用黄金分割点查找。若查找比黄金分割点的值小,则向比黄金分割点小的区域继续使用黄金分割点查找。这次依次查找,直到找到为止,原理有和二分查找相似,这是中点(mid)不一样。

那么关键就是如何查找到黄金分割点

黄金分割点查找:

  1. 黄金分割点查找要运用到斐波那契数列,要在该数列中从小到大找到一个数减一刚好大于等于待查找数组的长度,既顺序表长度n <= F[k] - 1
  2. 但顺序表长度 n 不一定刚好等于 F[k]-1,所以需要将原来的顺序表长度 n 增加至 F[k]-1(将最后一个字依次向后填充)。
  3. 这样一来带查找的顺序表就可以表示成:
    由斐波那契数列 F[k]=F[k-1]+F[k-2] 的性质,可以得到F[k]-1=(F[k-1]-1)+(F[k-2]-1)+1
    常用的查找算法(java实现)_第1张图片
    黄金分割点的位置:mid = left + F(k - 1) - 1
    注: 为什么是F[k] - 1,而不是F[k]?
    因为斐波那契数列只有从第二个数开始与后一个数的比值才越来越接近黄金比率

斐波那契数列的大小: 数组长度不同,那么对应所需要的斐波那契数列长度就不一样。若是写死,则很浪费空间。故斐波那契数列的大小可根据数组的大小进行选择。常用的查找算法(java实现)_第2张图片

  • 当数组长度小于等于5时数组的长度大于等于数组长度对应的斐波那契数,这时后面进行查找一个k使得fib[k]-1刚好大于等于数组长度就会越界,故写死,给一个6。产生的最大斐波那契数是8,大于数组长度,就不会越界
  • 当数组长度大于5时,数组的长度小于数组长度对应的斐波那契数,斐波那契数组长度为待顺序表长度,不会产生越界

实现动态斐波那契数列

public int[] fib( int right){
    int[] fibonacii;
    // 当数组长度小于等于5时,数组的长度大于等于数组长度对应的斐波那契数,
    // 这时后面进行查找一个k使得fib[k]-1刚好大于等于数组长度就会越界,故写死给一个6。产生的最大斐波那契数是8,大于数组长度,就不会越界
    if (right <= 5){
        fibonacii = new int[6];
    }else {//当数组长度大于5时,数组的长度小于数组长度对应的斐波那契数,不会产生越界
        fibonacii = new int[right];
    }

    fibonacii[0] = 1;
    fibonacii[1] = 1;
    for (int i = 2; i < fibonacii.length; i++) {
        fibonacii[i] = fibonacii[i -1] + fibonacii[i - 2];
    }
    return fibonacii;
}

斐波那契查找完整代码:

public class FibonacciSearch {
    public static void main(String[] args) {
        int[] arr = {1,5,90,500,1000};
        FibonacciSearch fibonacciSearch = new FibonacciSearch();
        int i = fibonacciSearch.fibonacciSearch(arr, 1000);
        System.out.println("i = " + i);
    }

    /**
     * @Description: fibonacciSearch 斐波那契查找
     * @param: [arr, searchValue]
     * @return: int
     * @auther: zqq
     * @date: 20/6/18 10:18
     */
    public int fibonacciSearch(int[] arr, int searchValue){
        int left = 0;
        int right = arr.length - 1;
        int[] fib = fib(arr.length);
        int mid = 0;// 存储
        int k = 0;
        while (arr.length > fib[k] - 1){ // 找到一个k使得这个斐波那契数刚好大于等于right
            k++;
        }
        if (arr.length < fib[k]){
            arr = Arrays.copyOf(arr, fib[k]);// 因为right可能小于找到的斐波那契数,所以需要对数组扩容进行向后填充
            for (int i = right+1; i < arr.length; i++) {
                arr[i] = arr[right];
            }
        }
        while (left <= right){
            mid = left + fib[k - 1] - 1; // 分割点
            if (searchValue < arr[mid]){ // 在黄金分割点左边
                right = mid - 1;
                k--;// 向fib[k-1]的范围查找
            }else if (searchValue > arr[mid]){
                left = mid + 1;
                k -= 2;//向fib[k-2]的范围查找
            }else {
                if (mid <= right){
                    return mid;
                }else {
                    return right;
                }
            }
        }
        return -1;// 没有找到
    }

    /**
     * @Description: fib 构造斐波那契数列
     * @param: [right, left]
     * @return: int[]
     * @auther: zqq
     * @date: 20/6/18 9:50
     */
    public int[] fib( int right){
        int[] fibonacii;
        // 当数组长度小于等于5时,数组的长度大于等于数组长度对应的斐波那契数,
        // 这时后面进行查找一个k使得fib[k]-1刚好大于等于数组长度就会越界,故写死给一个6。产生的最大斐波那契数是8,大于数组长度,就不会越界
        if (right <= 5){
            fibonacii = new int[6];
        }else {//当数组长度大于5时,数组的长度小于数组长度对应的斐波那契数,不会产生越界
            fibonacii = new int[right];
        }

        fibonacii[0] = 1;
        fibonacii[1] = 1;
        for (int i = 2; i < fibonacii.length; i++) {
            fibonacii[i] = fibonacii[i -1] + fibonacii[i - 2];
        }
        return fibonacii;
    }
}

需要查找重复的可再次基础上修改。

你可能感兴趣的:(数据结构与算法)