数据结构(Java)学习笔记——知识点:查找算法

十一、查找算法

1、基本介绍

 在Java中,我们常用的查找有四种:
 1)顺序(线性查找)
 2)二分查找 / 折半查找
 3)插值查找
 4)斐波那契查找

2、顺序(线性查找)

 有一个数列:{1,8,10,89,1000,1234},判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。

算法思路

 顺序查找就很简单了,只需要用一个for循环遍历数组,然后查找相应数字的下标就行了,没有丝毫的难度。
      请添加图片描述

代码实现

 public class SeqSearch {
    /*
    有一个数列:{1,8,10,89,1000,1234},
    判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
    */
    public static void main(String[] args) {
        int[] arr = {1,8,10,89,1000,1234};
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要查找的值:");
        int result = Search(arr,scanner.nextInt());
        if( result != -1){
            System.out.println("数组中存在该值,下标为:" + result);
        }else {
            System.out.println("数组中没有该值!");
        }
    }

    /**
    * @Author: Cui
    * @Description: 顺序查找指定的数字在数组中的下标
    * @DateTime:  22:46
    * @Params: arr 待查找的数组, num  待查找的数字
    * @Return
    */
    public static int Search(int[] arr, int num){
        for(int index = 0;index < arr.length; index++){
            if(num == arr[index]){
                return index;
            }
        }
        return -1;
    }
}

运行结果

请输入要查找的值:
1000
数组中存在该值,下标为:4

3、二分查找 / 折半查找

 有一个数列:{1,8,10,89,1000,1234},判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。

算法思路

 1)首先要对数组进行排序,然后确定数组的中间下标。两个left和right指针,分别指向数组的前后两端,mid = (left + right) / 2。
 2)然后让要查找到的数num和arr[mid]进行比较。
 3)如果num > arr[mid] ,就在右边递归进行查找。如果num < arr[mid],就在左边递归进行查找。num = arr[mid] 说明找到了。
 4)终止递归的条件是在数组中找到了相应的数字,或者是遍历完整个数组后还是没有找到,又或者是当left > right时就需要退出递归。
      请添加图片描述

代码实现

/*
有一个数列:{1,8,10,89,1000,1234},
判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
*/
public class BinarySearch {
    public static void main(String[] args) {
        int[] arr = {1, 8, 10, 89, 1000, 1234};
        int left = 0;
        int right = arr.length - 1;
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要查找的值:");
        int result = Search(arr,scanner.nextInt(),left,right);
        if( result != -1){
            System.out.println("数组中存在该值,下标为:" + result);
        }else {
            System.out.println("数组中没有该值!");
        }
    }

    /**
    * @Author: Cui
    * @Description: arr:数组  num:要查询的数组  left:左指针  right:右指针
    * @DateTime:  16:21
    * @Params: 二分法查找
    * @Return 返回-1表示没找到,返回下标表示找到
    */
    public static int Search(int[] arr, int num, int left,int right) {
        int mid = (left + right) / 2;
        if(left > right){
            return -1;//如果左边的值大于右边的值,就什么都没找到,返回-1
        }
        if(num < arr[mid]){
            right = mid -1;//在左边查询
        }else if(num > arr[mid]){
            left = mid + 1;//在右边查询
        }else{
            return mid;//就是这个值
        }
        return Search(arr,num,left,right);//递归
    }
}

运行结果

请输入要查找的值:
1234
数组中存在该值,下标为:5

 如果数列变成了这样:{1,8,10,89,1000,1000,1234},怎么查找才能把所有的东西都找到呢?
只需要在找到1000这个数的时候不要先返回,而是向左和右进行扫描,把数字是1000的下标都方法ArrayList中去。

代码实现(优化)

/*
有一个数列:{1,8,10,89,1000,1234},
判断数列中是否包含给定的查找条件的值,如果找到了,提示找到,并且给出下标值。
*/
public class BinarySearch {
    public static void main(String[] args) {
        int[] arr = {1, 8, 10, 89, 1000, 1000, 1000, 1000, 1234};
        int left = 0;
        int right = arr.length - 1;
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要查找的值:");
        ArrayList<Integer> arrayList = Search(arr,scanner.nextInt(),left,right);
        if(arrayList != null){
            System.out.println("数组中存在该值,下标为:" + arrayList);
        }else {
            System.out.println("数组中没有该值!");
        }
    }

    /**
    * @Author: Cui
    * @Description: arr:数组  num:要查询的数组  left:左指针  right:右指针
    * @DateTime:  16:21
    * @Params: 二分法查找
    * @Return 返回-1表示没找到,返回下标表示找到
    */
    public static ArrayList<Integer> Search(int[] arr, int num, int left,int right) {
        int mid = (left + right) / 2;
        if(left > right){
            return new ArrayList<>();//如果左边的值大于右边的值,就什么都没找到,返回-1
        }
        if(num < arr[mid]){
            right = mid -1;//在左边查询
        }else if(num > arr[mid]){
            left = mid + 1;//在右边查询
        }else{
            ArrayList<Integer> list = new ArrayList<Integer>();
            list.add(mid);//先把当前值保存了
            int midLeft = mid - 1;
            int midRight = mid + 1;
            //在这里对左右进行扫描,把符合条件的值都找到
            //向左查找
            while(true){
                if(midLeft < 0 || arr[midLeft] != num){//退出
                    break;
                }
                list.add(midLeft);
                midLeft -= 1;//下标左移
            }
            //向右查找
            while(true){
                if(midRight > arr.length - 1 || arr[midRight] != num){//退出
                    break;
                }
                list.add(midRight);
                midRight += 1;//下标右移
            }
            return list;
        }
        return Search(arr,num,left,right);//递归
    }
}

运行结果(优化)

请输入要查找的值:
1234
数组中存在该值,下标为:[8]

4、插值查找

 插值查找类似于二分查找,不同的是插值查找每次都是自适应的mid处开始查找。
 插值查找的操作很神奇,尽量带入数学的思想进行理解。

算法思路

将二分查找的公式改变一下:
 在这里插入图片描述
 其实就是变成,int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low])
 我们把这个等式移项,会得到:(mid - low) / (high - low) = (key - arr[low]) / (arr[high] - arr[low])
 这不就是数学中,两个相似三角形的斜边与直角边的比相等么。
 根据这个求出来的 mid 值,是会根据所要查找的值进行不断优化的。
 这种算法在顺序,归率的线性数组种,要比二分查找要快捷、准确的多。

代码实现

public class InsertValueSearch {
    public static void main(String[] args) {
        int[] arr = new int[100];
        for(int i = 0; i < arr.length; i++){
            arr[i] = i + 1;
        }
        int index = insertValueSearch(arr, 0, arr.length - 1, 78);
        System.out.println("下标为:" + index);
    }

    /**
    * @Author: Cui
    * @Description: arr:数组  num:要查询的数组  left:左指针  right:右指针
    * @DateTime:  16:40
    * @Params: 插值查找算法
    * @Return 如果找到了,返回对应的下标,如果没找到,返回-1
    */
    public static int insertValueSearch(int[] arr, int left, int right, int nums){
        if(left > right || nums < arr[0] || nums > arr[arr.length - 1]){
            return -1;
        }
        //求出mid
        int mid = left + (right + left) * (nums - arr[left]) / (arr[right] - arr[left]);
        int midValue = arr[mid];
        if(nums > midValue){
            //应该向右递归
            return insertValueSearch(arr, mid + 1, right, nums);
        } else if (nums < midValue) {
            //应该向左递归
            return insertValueSearch(arr, mid - 1, right, nums);
        }else{
            return mid;
        }
    }
}

运行结果

下标为:77

插值查找注意事项:
 1)对于数据量较大,数据分布较为平均的查找来说,用插值查找,速度更快。
 2)对于数据分布不平均的情况,该方法不一定比二分查找好。

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

 黄金分割点是指把一条线段分割为两部分,使其中一部分与全部的比等于另一部分与这部分的比。去前三位数字的近似值就是 0.618。
 斐波那契数列{1,1,2,3,5,8,13,21,34,55}。发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值 0.618。

算法思路

 斐波那契查找的原理和前两种相似,仅仅是改变了中间结点(mid)的位置。mid 不再是通过中间值或插值得到,而是在黄金分割点附近,即:mid = low + F(k - 1)- 1。(其中F代表斐波那契数列),如图所示:
在这里插入图片描述
 1)斐波那契数列 F[K] = F[K - 1] + F[K - 2] 的性质,可以得到 (F[K] - 1) = (F[K - 1] - 1) + (F[K - 2] - 1)+ 1。这个式子说明:只要顺序表的长度为 F[K] - 1,则可以将该表分为前后 F[K - 1] - 1 和 F[K - 2] - 1 这两段,就是像上图所示的一样。于是中间值为 mid = low + F(k - 1)- 1
 2)每一子段也可以用相同的方式进行分割。
 3)顺序表长度不一定刚好等于 F[K] - 1,所以需要将原来的顺序表长度 n 增加到 F[K] - 1。这里的 k 值只要使得 F[K] - 1 恰好大于或等于 n 即可,由以下代码得到,顺序表长度增加以后,新增的位置按照顺序赋值即可。

while(n > fib(k) - 1){
	k++;
}

代码实现

public class FibonacciSearch {
    public static int maxSize = 20;
    public static void main(String[] args) {
        int[] arr = {1,8,10,89,1000,1234};
        int index = fibSearch(arr, 1000);
        System.out.println("下标为:" + index);
    }

    //因为后面我们mid = low + F(k - 1) - 1,需要使用斐波那契数列
    //因此我们需要先获取到一个斐波那契数列
    //非递归得到一个斐波那契数列。
    public static int[] fib(){
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for(int i = 2; i < maxSize; i++){
            f[i] = f[i - 1] + f[i - 2];
        }
        return f;
    }

    /**
    * @Author: Cui
    * @Description: 用斐波那契查找
    * @DateTime:  13:05
    * @Params: a 待查找数组,num 要查找的数字
    * @Return 找到返回对应下标,找不到返回-1
    */
    //编写斐波那契查找算法
    public static int fibSearch(int[] a, int num){
        int mid;
        int low = 0;
        int high = a.length - 1;
        int k = 0;//表示斐波那契分割数值的下标
        int f[] = fib();//获取到斐波那契数列
        //得到斐波那契分割数值的下标
        while(high > f[k] - 1){
            k++;
        }
        //f[k]的值可能大于a的长度,因此需要用Arrays类,构造一个新的数组,把长度进行填充
        int[] temp = Arrays.copyOf(a,f[k]);
        for(int i = high + 1; i < temp.length; i++){
            temp[i] = a[high];
        }
        //用while来循环处理,找到我们的数num
        while(low <= high){
            mid = low + f[k - 1] - 1;
            if(num < temp[mid]){//小了,向左边查找
                high = mid - 1;
                k--;
            }else if (num > temp[mid]){//大了,向右边查找
                low = mid + 1;
                k -= 2;//
            }else{
                //找到了
                if(mid <= high){
                    return mid;
                }else{
                    return high;
                }
            }
        }
        return -1;//没找到,返回-1
    }
}

运行结果

下标为:4
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇直不会放弃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值