首页 > 编程学习 > 查找算法.

查找算法.

发布时间:2022/11/10 8:20:35

在java中,常用的查找有四种

  • 顺序查找
  • 二分查找/折半查找
  • 插值插值
  • 斐波拉查找

线性查找算法

有一个数列(1,8,10,89,1000,1234),判断数列中是否包含此名称[顺序查找],要求:如果找到了,就提示找到,并给出下标值,

思路:如果查到全部符合条件的值;

注意我们的线性查找我们找到一个就放回,如果里面有两个或多个相同的呢,我们可以返回值是一个list集合,然后我们遍历的时候全部遍历就行.

package com.atguigu.search;

public class SeqSearch {
    public static void main(String[] args) {
        int[] arr={1,9,11,-1,34,89};//没有顺序的数组
        int index=seqSearch(arr,11);
        if(index==-1) {
            System.out.println("没有找到11");
        }
        System.out.println("找到了,下标为"+index);
    }


    /**
     * 这里我们实现的线性查找是找到一个满足条件的值就返回,
     * @param arr
     * @param value
     * @return
     */
    public static int seqSearch(int[] arr,int value){
        //线性查找逐一比对,发现有相同值,则返回下标
        for(int i=0;i<arr.length;i++){
            if(arr[i]==value){
                return i;
            }
        }
        return -1;
    }
}

二分查找

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

二分查找的思路

  • 首先确定该数组中间的下标
    • mid=(left+right)/2
  • 然后让需要查找的数findVal和arr[mid]比较
    • findVal>arr[mid],说明你要查找的数在mid的右边,因此需要递归向右查找
    • findVal<arr[mid],说明你要查找的数在mid的左边,因此你要递归的向左查询
    • findVal=arr[mid],说明找到了,找到就返回
      什么时候结束递归
  • 找到就结束递归
  • 递归完整个数组,仍然没有找到findVal也要结束递归,此时的结束条件
    • 当left>right就需要退出,这个是我们没有找到结束的递归条件
二分查找的基本写法

package com.atguigu.search;
//注意二分查找的前提是数组必须是有序的(从小到大,从大到小都可以)
public class BinarySearch {
    public static void main(String[] args) {
        int[] arr={1,8,10,89,1000,1234};

        int resIndex=binarySearch(arr,0,arr.length-1,88);
        System.out.println(resIndex);
    }


    /***
     * 二分查找算法
     * @param arr  数组
     * @param left  左边的索引
     * @param right  右边的索引
     * @param findVal 要查找的值
     * @return  如果找到放回下标,如果没有找到,返回-1
     */

    public static int binarySearch(int[] arr,int left,int right,int findVal){

        //当left>right时,说明递归完毕,但是没有找到
        if(left>right){
            return -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;
        }
    }
}

二分查找的升级写法

当一个数组中{1,8,10,89,1000,1000,1234} 当一个有序数组中,有多个相同的数值时,如何将多有数组都查找到,比如这里的1000

思路

  • 在找到mid值时,不要马上返回,先向mid这个索引值的左边扫描,将所有满足1000的原始的下标加入到一个集合中,ArrayList
  • 向mid索引值的右边扫描,将所有满足1000的元素下标,加入到集合ArrayList
  • 将ArrayList集合返回

package com.atguigu.search;

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

//注意二分查找的前提是数组必须是有序的(从小到大,从大到小都可以)
public class BinarySearch {
    public static void main(String[] args) {
        int[] arr={1,8,10,89,1000,1234};
        int[] arr1={1,8,10,89,1000,1000,1234};

        int resIndex=binarySearch(arr,0,arr.length-1,88);
        System.out.println(resIndex);

        ArrayList<Integer> list=binarySearch1(arr1,0,arr1.length-1,1000);
        System.out.println(list);
    }


    /***
     * 二分查找算法
     * @param arr  数组
     * @param left  左边的索引
     * @param right  右边的索引
     * @param findVal 要查找的值
     * @return  如果找到放回下标,如果没有找到,返回-1
     */

    public static int binarySearch(int[] arr,int left,int right,int findVal){

        //当left>right时,说明递归完毕,但是没有找到
        if(left>right){
            return -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 ArrayList<Integer> binarySearch1(int[] arr,int left,int right,int findVal){

        //当left>right时,说明递归完毕,但是没有找到
        if(left>right){
            return new ArrayList<Integer>();//返回一个空的ArrayList
        }
        int mid=(left+right)/2;
        int midVal=arr[mid];
        if (findVal>midVal){ //向右边递归
            return binarySearch1(arr,mid+1,right,findVal);
        }else if(findVal<midVal){//向左递归
            return binarySearch1(arr,left,mid-1,findVal);
        }else {

            ArrayList<Integer> arrayList=new ArrayList<Integer>();
            //向mid索引值左边扫描,满足1000,的元素下标,加入到集合
            int temp=mid-1;
            while (true){
                if(temp<0||arr[temp]!=findVal){//退出
                    break;
                }
                //否则将temp放入到集合中
                arrayList.add(temp);//这里用来自动装箱
                temp-=1;//temp左移动
            }

            //
            //向mid索引值右边边扫描,满足1000,的元素下标,加入到集合
             temp=mid+1;
            while (true){
                if(temp>arr.length-1||arr[temp]!=findVal){//退出
                    break;
                }
                //否则将temp放入到集合中
                arrayList.add(temp);//这里用来自动装箱
                temp+=1;//temp右边移动
            }
            arrayList.add(mid);
            return arrayList ;
        }
    }
}

插值查找

  • 插值查找算法类似于二分查找,不同的是插值查找每次从自适应mid处开始查找.
  • 将折半查找中的求mid索引的公式,low表示左边索引,high表示右边索引
  • mid=(low+high)/2=low+1/2(high-low) 改为mid=low+(key-a[low])/(a[high]-a[low]*(high-low))
  • 公式里面的可以就是我们要查找的值,findVal
    在这里插入图片描述
  • int midIndex=low+(high-low)*(key-arr[low])/(arr[high]-arr[low]);/插值索引 /
  • 对应前面的代码,我们的公式变成了
  • int mid=left+(right-left)*(findVal-arr[Left])/(arr[right]-arr[left])
插值查找算法的举例说明

数组arr={1,2,3,4…,100}
假入我们需要查找的值是1
使用二分查找我们需要多次递归我们才能找到1
使用插值查找算法
int mid=left+(right-left)(findVal-arr[left])/(arr[right]-arr[left])
int mid=0+(99-0)
(1-1)/(100-1)=0+99*0/99=0

package com.atguigu.search;

import java.util.Arrays;

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;
        }
        int index=insertValueSearch(arr,0,arr.length-1,100);
        System.out.println("index="+index);
    }

    //编写插值查找算法

    /**
     *
     * @param arr 数组
     * @param left  左边索引
     * @param right 右边索引
     * @param findVal  查找值
     * @return 如果找到返回对应下标,没有找到返回-1
     */
    public static int insertValueSearch(int[] arr,int left,int right,int findVal){
        //注意这个判断必须有,不然可能会越界
        if(left>right||findVal<arr[0]||findVal>arr[arr.length-1]){
            //插值查找算法也要求数组是有序的,所有上面的条件我们直接返回-1
            return -1;
        }
        //求出mid
        int mid=left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
        int midVal=arr[mid];
        if(findVal>midVal){//如果要查的值比我们arr[mid]大,那么我们向右边递归查找
            return insertValueSearch(arr,mid+1,right,findVal);

        }else if(findVal<midVal){//向左边递归查找
            return insertValueSearch(arr,left,mid-1,findVal);
        }else {
            return mid;
        }

    }

}

插值查找注意事项

插值查找对于数据量较大关键字分布均匀的查找表来说,采用插值查找,速度较快
关键字分布不均匀的请求下,不一定比折半查找要好.

斐波拉契(也叫黄金分割法)

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

仅仅改变了中间点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即**(mid=low+F(k-1)-1)(F代表斐波拉契数列,K代表的第几个元素),如图所示:
在这里插入图片描述
对F(k-1)-1的理解
1,由于斐波拉契数列F[k]=F[k-1]+F[k-2]的性质得到,可以得到**(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,但顺序长度n不一定刚好等于F[k]-1,所有需要将原来顺序表长度增加到F[k]-1,这里的k值只要能是的F[k]-1,恰好大于或等于n即可,有以下代码得到,顺序表长度增加后,新增的位置(从n+1到F[k]-1位置),都赋为n即值即可.
while(n>fib[k]-1){
k++}
这种就是需要对原来的数组进行扩容

package com.atguigu.search;

import java.util.Arrays;

public class FibonacciSearch {
    
    public static int maxSize=20;
    public static void main(String[] args) {
        int [] arr={1,8,10,89,1000,1234};
        System.out.println("index="+fibSearch(arr,8));
    }
    
    //因为后面我们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;
    }
    
    //编写斐波拉契查找算法
//使用非递归的方式编写算法
    /**
     * 
     * @param a 数组
     * @param key 我们需要查找的关键码
     * @return 返回对应的下标,如果没有返回-1
     */
    public static int fibSearch(int[] a,int key) {
        int low = 0;
        int high = a.length - 1;
        int k = 0;//表示斐波拉契分割数值对应的下标
        int mid = 0;//存放我们的mid值
        int[] f = fib();//获取到斐波拉契数列
        //获取到斐波拉契分割数值的下标
        while (high > f[k] - 1) {
            k++;
        }
        //因为f[k]这个值可能大于我们数组a的长度,因此我们需要使用一个Arrays类,构造一个新的数组,并指向temp
        //Arrays.拷贝后,不足的部分用0填充
        int[] temp = Arrays.copyOf(a, f[k]);
        //实际上需要使用a数组最后的数填充 temp
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = a[high];
        }

        //使用while来循环处理,找到我们的数key
        while (low <= high) {//只要这个条件满足,就可以一直找
            mid = low + f[k - 1] - 1;
            if (key < temp[mid]) {//说明我们应该继续向数组的前面查找(左边)
                high = mid - 1;
                //为什么是k--
                // 说明
                //1,全部元素等于前面元素加上我们的后面的元素
                //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=f[k-1-1]-1
                k--;
            } else if (key > temp[mid]) {//向数组的后面(右边)查找
                low = mid + 1;
                //问什么是k-2
                //说明
                //全部元素前面加后面
                //f[k]=f[k=1]+f[k-2]那拿到
                //因为后面我们后面还有f[k-2]个元素,所有我们可以继续拆分f[k-1]=f[k-3]+f[k-4]
                //即在f[k-2]的前面查找k-=2
                //即下次循环mid=f[k-1-2]-1;
                k -= 2;
            } else {//找到
                //需要确定,返回的是哪个下标
                if (mid <= high) {
                    return mid;
                } else {
                    return high;
                }
            }
        }
        return -1;
    }
}

斐波拉契算法小结

斐波拉契仍然是有序的,还有对于对F(k-1)-1的理解要好好理解.
然后多看代码.

Copyright © 2010-2022 dgrt.cn 版权所有 |关于我们| 联系方式