常用的查找算法


常用的查找算法

1, 正常的顺序查找

a07_01_NormalSearch

就是遍历,没啥内容

package im.bool.a07_search_algrithm;

/**
 * #Author : ivanl001
 * #Date   : 2020/2/29 6:06 PM
 * #Desc   : 正常的顺序查找
 **/
public class a07_01_NormalSearch {

    public static void main(String[] args) {
        int[] nums = {1, 9, 11, -1, 35, 89};

        int i = normalSearch(nums, -1);
        System.out.println(i);
    }

    public static int normalSearch(int[] nums, int value) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == value) {
                return i;
            }
        }
        return -1;
    }
}

2, 二分查找算法

a07_02_BinarySearch

  • 有序数组中查找才有意义

  • 注意:迭代的时候,binarySearch01(nums, left, midIndex-1, findValue), 需要把midIndex左移一位或者右移一位

package im.bool.a07_search_algrithm;

import java.util.ArrayList;
import java.util.List;

/**
 * #Author : ivanl001
 * #Date   : 2020/3/1 6:31 PM
 * #Desc   : 二分查找算法
 **/
public class a07_02_BinarySearch {

    public static void main(String[] args) {
        int[] nums = {1, 8, 10, 89, 89, 89, 89, 89, 89, 1000, 1234};

//        int i = binarySearch(nums, 0, nums.length - 1, 89);
//        System.out.println(i);

        List<Integer> integers = binarySearch01(nums, 0, nums.length - 1, 89);
        System.out.println(integers);
    }

    //如果有多个findValue在数组中,本方法就只能找出一个
    public static List<Integer> binarySearch01(int[] nums, int left, int right, int findValue) {

        //这里可以优化一下,如果小于最小数或者大于最大数,或者left > right,即可提前终止
        if (findValue > nums[nums.length - 1] || findValue < nums[0] || left > right) {
            return null;
        }

        int midIndex = (left+right)/2;
        int midValue = nums[midIndex];

        if (left > right) {
            return null;
        }

        if (midValue > findValue) {
            return binarySearch01(nums, left, midIndex-1, findValue);
        } else if (midValue < findValue) {
            return binarySearch01(nums, midIndex+1, right, findValue);
        } else{
            //如果既不大于,也不小于,那么就是等于了
            List<Integer> result = new ArrayList<Integer>();

            //然后再往左边找
            for (int i = midIndex-1; i >= left; i--) {
                if (nums[i] == findValue) {
                    result.add(i);
                } else {
                    break;
                }
            }

            //先往右边找
            for (int i = midIndex; i <= right; i++) {
                if (nums[i] == findValue) {
                    result.add(i);
                } else {
                    break;
                }
            }


            return result;
        }
    }

    //如果有多个findValue在数组中,本方法就只能找出一个
    public static int binarySearch(int[] nums, int left, int right, int findValue) {

        //这里可以优化一下,如果小于最小数或者大于最大数,或者left > right,即可提前终止
        if (findValue > nums[nums.length - 1] || findValue < nums[0] || left > right) {
            return -1;
        }

        int midIndex = (left+right)/2;
        int midValue = nums[midIndex];

        if (left > right) {
            return -1;
        }

        if (midValue > findValue) {
            return binarySearch(nums, left, midIndex-1, findValue);
        } else if (midValue < findValue) {
            return binarySearch(nums, midIndex+1, right, findValue);
        } else{
            //如果既不大于,也不小于,那么就是等于了
            return midIndex;
        }
    }
}

3, 插值查找算法

a07_03_InterpolationSearch

差值查找算法其实就是二分查找的比例化

int midIndex = left + (right-left)*(findValue-nums[left])/(nums[right]-nums[left]);

package im.bool.a07_search_algrithm;

import java.util.ArrayList;
import java.util.List;

/**
 * #Author : ivanl001
 * #Date   : 2020/3/1 7:05 PM
 * #Desc   : 插值查找算法:二分查找算法有一个问题:如果一个八万的数组,查找的数据是第一个,那么需要从中间开始计算查找。这样就会有很多无用的查找。
 **/
public class a07_03_InterpolationSearch {
    public static void main(String[] args) {

        int[] nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

        List<Integer> integers = interpolationSearch(nums, 0, nums.length - 1, 15);
        System.out.println(integers);

    }

    public static List<Integer> interpolationSearch(int[] nums, int left, int right, int findValue){

        System.out.println("ivanl0001");

        //这里可以优化一下,如果小于最小数或者大于最大数,或者left > right,即可提前终止
        //注意:插入排序一定要有findValue < nums[0],findValue > nums[nums.length - 1], 要不然查询的数巨大,那么midIndex直接就过了
        if (findValue > nums[nums.length - 1] || findValue < nums[0] || left > right) {
            return null;
        }

        //插入算法和二分查找算法最根本的区别就是中间值的计算
        //int midIndex = (left+right)/2;
        int midIndex = left + (right-left)*(findValue-nums[left])/(nums[right]-nums[left]);

        int midValue = nums[midIndex];
        if (left > right) {
            return null;
        }

        if (midValue > findValue) {
            return interpolationSearch(nums, left, midIndex-1, findValue);
        } else if (midValue < findValue) {
            return interpolationSearch(nums, midIndex+1, right, findValue);
        } else {
            //如果既不大于,也不小于,那么就是等于了
            List<Integer> result = new ArrayList<>();

            //然后再往左边找
            for (int i = midIndex - 1; i >= left; i--) {
                if (nums[i] == findValue) {
                    result.add(i);
                } else {
                    break;
                }
            }

            //先往右边找
            for (int i = midIndex; i <= right; i++) {
                if (nums[i] == findValue) {
                    result.add(i);
                } else {
                    break;
                }
            }
            return result;
        }
    }
}

4, 斐波那契查找算法

这个算法和二分查找及插值查找算法的不同的地方依然是中间值的逻辑不一样

https://zq99299.github.io/dsalg-tutorial/dsalg-java-hsp/08/04.html#%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86

基本介绍

黄金分割 点是指把一条 线段 分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近视值是 0.618。由于按此比例设计的造型十分美丽,因此称为 黄金分割,也称为 中外比。这是一个神奇的数字,会带来意想不到的效果。

image-20201018220112518

简单说,两条线的比例为 1:1.618,比如上图的头和身体的比例、鼻子和嘴巴下巴的比例

斐波那契数列 {1, 1, 2, 3, 5, 8, 13, 21, 34, 55 } 发现斐波那契数列的 两个相邻数的比例,无限接近 黄金分割值 0.618

简单说:

3/2=1.5 、5/3=1.667、8/5=1.6、13/8=1.625 这样看来,他们的比例值是无限接近的 1.618 的
1/2=0.5 、3/5=0.6、5/8=0.625、8/13=0.6125 这样看,他们的比例值是无限接近  0.618 的

工作原理

与前两种(二分查找/插值查找算法)类似,只是改变了 mid 值

要从这个数组 arr = {1, 8, 10, 1000} 中查找数值 1000
斐波那契数列:{1, 1, 2, 3, 5, 8, 13, 21, 34, 55 }
1. 在斐波那契数列中找到符合这个数组长度的数值,如果没有,则找最近的一个
   arr.length = 4, 在斐波那契数列中,没有 4 这个值,最近的一个是数值 5 ,index = 4
   那么 k = 4
2. 要将原数组的长度扩充为这个 k 对应的数值 5,多余出来的 1 个数字用原数组 arr 的最后一个值填充
   也就是:
      arr = {1, 8, 10, 1000}  扩充成如下
   newArr = {1, 8, 10, 1000,1000}
3. 计算 mid 值

他的公式是 mid = low + F(k-1) -1

怎么理解呢?由上述所知,k = 4,

{1,  1,  2,  3,  5, 8, 13, 21, 34, 55 }
                 ↑
             ↑   k
         ↑  k-1
        k-2

斐波那契数列的特性是除了相邻数的比例是无限接近黄金分割值,还有一个特性是:相邻的两个数相加等于后一个数

就如上所述: 2 + 3 = 5 ,3 + 5 = 8;那么其中变量解释如下:

  • mid :就是我们要对比的值索引
  • low:该线段的最左端
  • F(k-1)F(k-2):分别表示这个数组个数左右两端的个数各是多少

那么 arr = {1, 8, 10, 1000} 的长度是 4,补充为斐波那契数列中的值后,新的数组长度是 5

k = 4
{1,  1,  2,  3,  5, 8, 13, 21, 34, 55 }
当 low = 0 时:mid = 0 + 3 - 1 = 2
当 low = 1 时:mid = 1 + 3 - 1 = 3
当 low = 2 时:mid = 2 + 3 - 1 = 4 
当 low = 3 时:mid = 3 + 3 - 1 = 5 , 此时新数组长度为 5,最大下标为 4,已经超过该数组最大个数了,可以认为没有找到。
上面的验证是验证这个公式是否有效,至少不会出现数组越界的情况

那么再来推导验证下改变 k 的值,上面的 k = 4

当 k = 3 ,low = 0 : mid = 0 + 2 -1 = 1
当 k = 2 ,low = 0 : mid = 0 + 1 -1 = 0
当 k = 1 ,low = 0 : mid = 0 + 1 -1 = 0

可以看到移动其中的 k 也是不会导致数组越界的。

那么再来看,k、k-1k-2 有啥作用

      arr = {1, 8, 10, 1000}  扩充成如下
   newArr = {1, 8, 10, 1000,1000}
   
{1,  1,  2,  3,  5, 8, 13, 21, 34, 55 }
                 ↑
             ↑   k
         ↑  k-1
        k-2
扩充后的数组长度为 5
            - - - - -      这一条线的长度为 5 ,也就是 k = 4
              ↑
 左侧长度为  2         右侧长度为 3 

 
        Copied!
    

那么再来看下面的图:

image-20201018220112519

斐波那契数列性质 F[k] = F[k-1] + F[k-2]

由以上性质可以得到上图的组合: F[k]-1 = (F[k -1] -1) + (F[k-2] -1) + 1

上图的公式 F[k - 1] - 1 + 中间 mid 占用 1 个位置 + F[k - 2] -1 + 1 个位置 就是这个数组的所有元素个数。

那么说明:只要顺序表的长度为 F[k]-1,则可以将该表分成 长度为 F[k-1]-1F[k-2]-1 两段,如上图所示

那么中间值则为:mid = low + F[k-1]-1

上面说了这么多,其实也并没有说明白他的这个求中间值的公式为什么是这样,只是得到了最重要的几个信息:

  1. 求中间值是通过斐波那契数列来计算的
  2. 原始数组和斐波那契数列的关系是:
    1. 原始数组的长度必须扩充至斐波那契数列中某一个值的长度
    2. 因为可以通过斐波那契数列的性质,将这一个扩充数组分成黄金分割的两段
    3. 分成两段后,就可以查找中间值,而这个中间值则可称为黄金分割点
    4. 查找该点,是否是所要查找的值,如果不是,则根据大小,可继续将某一段继续分割,查找他的黄金分割点
  3. k:进行 k 的减少或增加,必然可以根据斐波那契数列的特性,将数列分成两段

好了,个人理解差不多就只能这样了,下面看一遍代码实现,你就明白了,只能感叹数学之美。

a07_04_FibonacciSearch

package im.bool.a07_search_algrithm;

import java.util.Arrays;

/**
 * #Author : ivanl001
 * #Date   : 2020/3/5 8:16 PM
 * #Desc   : 斐波那契查找算法其实就是把原来的二分改为黄金分,这里我就不写代码了
 **/
public class a07_04_FibonacciSearch {

    // 超过45的话, 就基本会超过int的最大值
    public static int maxFibSize = 45;

    public static void main(String[] args) {
        int[] nums = {1, 8, 10, 34, 88, 89, 1000, 1234};
//        int[] nums = {1, 8, 9};

        int i = fibonacciSearch(nums, 89);
        System.out.println("对应的索引是:" + i);
        if (i != -1) {
            System.out.println("对应的值是:" + nums[i]);
        }


        /*int[] fib = fib();
        System.out.println(Arrays.toString(fib));*/

    }

    // 使用斐波那契查找算法,需要先设置好斐波那契数列
    public static int[] fib() {
        int[] f = new int[maxFibSize];
        int f0 = 1;
        int f1 = 1;

        f[0] = f0;
        f[1] = f1;

        int i=2;
        while (i < maxFibSize) {

            f[i] = f[i - 1] + f[i - 2];
            i++;
        }
        return f;
    }

    /**
     * 这里不需要左值和右值,因为是用fibonacci进行分割的。先把当前的数组组装成fibonacci值,然后向下切分
     * @param nums
     * @param findValue
     * @return
     */
    private static int fibonacciSearch(int[] nums, int findValue) {

        int leftIndex = 0;
        int rightIndex = nums.length-1;

        // 这个是fibonacci值的索引
        int k = 0;

        int mid = 0;

        // 构造出fibonacci数列
        int f[] = fib();

        // 把nums的rightIndex个数字按照fibonacci先构造出来
        while (rightIndex > f[k] - 1) {
            k++;
        }

        // 因为当前数组个数可能是比f[k]-1是小的,所以拼接
        int[] temp = Arrays.copyOf(nums, f[k]);
        for (int i = rightIndex + 1; i < temp.length; i++) {
            temp[i] = temp[rightIndex];
        }

        // 数组已经组装完成,开始使用斐波那契进行切分
        while (leftIndex <= rightIndex) {
            mid = leftIndex + f[k - 1] - 1;
            // 说明是在左侧, k-1
            if (findValue < temp[mid]) {
                rightIndex = mid - 1;
                k -= 1;
            }
            // 说明是在右侧, k-2部分
            else if (findValue > temp[mid]) {
                leftIndex = mid + 1;
                k -= 2;
            } else {
                // rightIndex 是需要查找的最右侧。但是mid有可能大于rightIndex,因为右侧有填充
                if (mid <= rightIndex) {
                    return mid;
                }
                // 当 mid 值大于最高点的话
                // 也就是我们后面填充的值,其实他的索引就是最后一个值,也就是 high
                else {
                    return rightIndex;
                }
            }
        }

        return -1;
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值