10.查找算法

在java中,我们常用四种查找算法:

  1.顺序查找(线性)

  2.二分法/折半查找

  3.插值查找

  4.斐波那契查找

1.线性查找.

 

2.二分查找算法

二分查找:

对一个 进行二分查找{1,8,10,89,1000,1234},输入一个数,看看该数组是否存在此数,并且求出下标,如果没有就提示"没有这个数"  

样例:
public class BinarySearch {
    public static void main(String[] args) {
        int arr[] = {1, 8, 10, 89, 1000, 1234};
        int index = binarySearch(arr, 0, arr.length-1, 5);
        System.out.println("找到了,下标为:" + index);
    }

    /**
     * 二分查找算法
     *
     * @param arr         数组
     * @param left        左索引
     * @param right       右索引
     * @param searchValue 查找数字
     * @return 找到的元素下标, 没找到返回-1
     */
    public static int binarySearch(int[] arr, int left, int right, int searchValue) {
        //当左索引大于右索引时
        if (left > right) {
            return -1;
        }
        //找到中间位置下标
        int middle = (left + right) / 2;
        if (searchValue < arr[middle]) {
            //这里需要注意,middle-1,不这么操作会造成死递归
            return binarySearch(arr, left, middle-1, searchValue);
        } else if (searchValue > arr[middle]) {
            //这里需要注意,middle+1
            return binarySearch(arr, middle+1, right, searchValue);
        } else {
            return middle;
        }
    }
}

思考:在{1,8,10,89,1000,1000,1000,1000,1234}中输出1000的所有下标

 public static List binarySearch2(int[] arr, int left, int right, int searchValue) {
        //当左索引大于右索引时,没找到
        if (left > right) {
            return null;
        }
        //找到中间位置下标
        int middle = (left + right) / 2;
        if (searchValue < arr[middle]) {
            //这里需要注意,middle-1,不这么操作会造成死递归
            return binarySearch2(arr, left, middle - 1, searchValue);
        } else if (searchValue > arr[middle]) {
            //这里需要注意,middle+1
            return binarySearch2(arr, middle + 1, right, searchValue);
        } else {
            /*
                重点1:
                这里在返回之前向左,和向右扫描,拿到所有相同的数的下标
             */
            List<Integer> list = new ArrayList<>();
            list.add(middle);
            int i = middle;
            //向左找
            while (arr[i - 1] == searchValue) {
                list.add(i - 1);
                i -= 1;
            }
            //向右找
            i = middle;
            while (arr[i + 1] == searchValue) {
                list.add(i + 1);
                i += 1;
            }
            return list;
        }
    }

3.插值查找算法

 

示例:
public class InsertValueSearch {
    private static int count=1;
    public static void main(String[] args) {
        int[] arr = new int[100];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
        int index=insertValueSearch(arr,0,arr.length-1,1);
        System.out.println("找到了:index="+index);
    }

    /**
     * 二分查找算法
     *
     * @param arr         数组
     * @param left        左索引
     * @param right       右索引
     * @param searchValue 查找数字
     * @return 找到的元素下标, 没找到返回-1
     */
    public static int insertValueSearch(int[] arr, int left, int right, int searchValue) {
        System.out.println("第"+(count++)+"次查找..");
        //当左索引大于右索引时
        if (left > right || searchValue < arr[0] || searchValue > arr[arr.length - 1]) {
            return -1;
        }
        //这里的下标按照这个公式来查找
        int middle = left + (right + left) * (searchValue - arr[left]) / (arr[right] - arr[left]);
        if (searchValue < arr[middle]) {
            //这里需要注意,middle-1,不这么操作会造成死递归
            return insertValueSearch(arr, left, middle - 1, searchValue);
        } else if (searchValue > arr[middle]) {
            //这里需要注意,middle+1
            return insertValueSearch(arr, middle + 1, right, searchValue);
        } else {
            return middle;
        }
    }
}

测试:
    线性的数组中,找几都是一次找到,但是必须是线性增加的数组..

插值查找的注意事项:

  1.对于数据量较大关键字分布比较均匀的查找表来说,采用插值查找速度较快。  

  2.关键字分布不均匀的情况下,该方法不一定比折半查找要好。

 

斐波那契(黄金分割法)查找算法

简介:

  

当解析到公式为:

  (f[k]-1)=(f[k-1]-1)+(f[k-2]-1)+1 并且顺序表的长度为f[k]-1时

发现等式分为三部分:f[k-1]-1     f[k-2]-1     1

最后的1就是mid,对照图比可以理解!

如果队列长度不是刚好的f[k]-1呢,参考截图3  

从而得到:mid=low+f[k-1]-1.

/**
 * 斐波那契查找算法
 * 借助额外数组(斐波那契数组:前两个元素的和等于第三个数组)来找到中间点
 */
public class FibonacciSearch {
    public static void main(String[] args) {
        int[] arr = {1, 9, 11, 18, 90, 91};
        System.out.println("原数组:" + Arrays.toString(arr));
        int index = fibSearch(arr, 18);
        System.out.println("index=" + index);
    }

    /**
     * 获取一个斐波那契数组
     *
     * @return 斐波那契数组
     */
    public static int[] fib() {
        int[] arr = new int[20];
        arr[0] = 1;
        arr[1] = 1;
        for (int i = 2; i < arr.length; i++) {
            arr[i] = arr[i - 1] + arr[i - 2];
        }
        return arr;
    }

    /**
     * 借助斐波那契数组查找下标
     *
     * @param arr 查找数组
     * @param key 查找的值
     * @return 下标
     */
    public static int fibSearch(int[] arr, int key) {
        int low = 0;
        int high = arr.length - 1;
        //表示斐波那契分割数值的下标
        int k = 0;
        //存放mid值
        int mid = 0;
        //获取斐波那契数组
        int f[] = fib();
        /*
            获取斐波那契分割值下标
            根据上述的推导公式得:
                1.f[k]=f[k-1]+f[k-2];
                    左边的等式-1,右边减两个1,再加一
                2.f[k]-1=(f[k-1]-1)  +  (f[k-2]-1)  +1
                3.当前提条件,顺序表的长度==f[k]-1时,可以将其分为三部分
                    1:f[k-1]-1
                    2:f[k-2]-1
                    3:后面加的1,这个1就是中间的mid
                4.mid=low+f[k-1]-1
         */
        System.out.println("斐波那契数组:" + Arrays.toString(f));
        while (high > f[k] - 1) {
            k++;
        }
        System.out.println("斐波那契-index:" + k + " value=" + f[k]);
        //因为f[k]的值可能大于arr的长度,因此将多出的部分以arr最后一个元素填充
        int[] temp = Arrays.copyOf(arr, f[k]);//将数组扩充到f[k]大小,然后填充值
        //例如:temp={1,2,3,0,0,0}--->{1,2,3,3,3,3}
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = arr[high];
        }
        System.out.println("转换后数组:" + Arrays.toString(temp));
        //通过while循环找到我们的数key
        while (low <= high) {
            mid = low + f[k - 1] - 1;
            System.out.println("mid:" + mid);
            if (key < temp[mid]) {
                //向左继续查找
                high = mid - 1;
                /*
                    为甚么k--;
                    说明:
                        1.全部元素=前面的元素+后面的元素
                        2.f[k]=f[k-1]+f[k-2]
                        因为前面有f[k-1]个元素,所以可以继续拆分f[k-1]=f[k-2]+f[k-3]
                        即在f[k-1]的前面继续查找k--
                        即下次循环 mid=low+f[k-1-1]-1
                 */
                k--;
            } else if (key > temp[mid]) {
                //向右继续查找
                low = mid + 1;
                /*
                    为什么为k-=2呢;
                    1.全部元素=前面的元素+后面的元素
                        2.f[k]=f[k-1]+f[k-2]
                        因为后面有f[k-2]个元素,所以可以继续拆分f[k-2]=f[k-3]+f[k-4]
                 */
                k -= 2;
            } else {
                //找到了
                if (mid <= high) {
                    return mid;
                } else {
                    return high;
                }
            }
        }
        return -1;
    }
}

测试输出:
    原数组:[1, 9, 11, 18, 90, 91]
    斐波那契数组:[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
    斐波那契-index:5 value=8
    转换后数组:[1, 9, 11, 18, 90, 91, 91, 91]
    mid:4
    mid:2
    mid:3
    index=3

 重点总结:通过额外创建一个数组:斐波那契数组,根据其特性去查找mid

posted @ 2022-11-29 21:09  努力的达子  阅读(57)  评论(0编辑  收藏  举报