三种常用的查找算法

让人想犯罪 __ 提交于 2019-12-06 03:16:56

在最近的复习中,我复习三种常用的查找算法,它们分别是:

1 线性查找

2 二分查找

3 插值法

4 斐波那契查找

线性查找

首先我们进行线性查找的讨论,对于线性查找,我们所做的操作就遍历数组同时逐一比对找出相匹配的元素,具体代码如下

    public int search(int[] arr,int value)
    {
        for(int i=0;i<arr.length;i++)
        {
            if(arr[i]==value)
            {
                return i;
            }
        }
        return -1;s
    }

又上述代码我们不难理解,线性查找就是遍历数组的同时逐一比对数组的值和要查找的值,如果相同则就返回对应的数组下标

二分查找

对于二分查找,我们首先计算mid的值,在此演示中,我们设置的mid值为(left+right)/2,如果要搜索的值大于数组下标为mid的值,则就将left的值设置为mid+1,继续遍历,如果要插入的值小于数组下标为mid的值,则就将right的值设置为mid-1,继续遍历,重复上述操作,直到找到对应的数组的下标为止,当发生left>right时,则说明数组中并没有要搜索的值,此时我们返回-1并结束循环。

  public static int binarySearch(int[] arr,int left,int right,int findVal)
    {
        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 List<Integer> binarySearch2(int[] arr,int left,int right,int findVal)
    {
        if(left>right)
        {
            return new ArrayList<Integer>();
        }
        int mid=(left+right)/2;
        int midVal=arr[mid];
        if(findVal>midVal)
        {
            return binarySearch2(arr,mid+1,right,findVal);
        }else if(findVal<midVal)
        {
            return binarySearch2(arr,left,mid-1,findVal);
        }else{
            List<Integer> list=new ArrayList<Integer>();
            int temp=mid-1;
            while (true)
            {
                if(temp<0||arr[temp]!=findVal)
                {
                    break;
                }
                //否则就把temp放入到集合中
                list.add(temp);
                temp-=1;
            }
            list.add(mid);
            temp=mid+1;
            while (true)
            {
                if(temp>arr.length-1||arr[temp]!=findVal)
                {
                    break;
                }
                //否则就把temp放入到集合中
                list.add(temp);
                temp+=1;
            }
            return list;
        }
    }

在经过分析时,我们发现搜索边缘的操作对于二分查找太过繁琐,所以我们引入插值法来进行搜索

插值法

插值法与二分查找相同,不过mid值有所改变

  /**
     * 插值法和二分查找法一样,查找数组必须是有序的
     * @param arr
     * @param left
     * @param right
     * @param findVal
     * @return
     */
    public static int insertValueSearch(int[] arr,int left,int right,int findVal)
    {
        if(left>right||findVal<arr[0]||findVal>arr[arr.length-1])
        {
            return -1;
        }
        int mid=left+(right-left)*(findVal-arr[left])/(arr[right]-arr[left]);
        int midVal=arr[mid];
        if(findVal>midVal)
        {
            return insertValueSearch(arr,mid+1,right,findVal);
        }else if(findVal<midVal)
        {
            return insertValueSearch(arr,left,mid-1,findVal);
        }else{
            return mid;
        }
    }

与二分查找方法不同是,插值法的mid等于low+(high-left)*(findVal-arr[low])/(arr[high]-arr[low]),我们在此基础上继续查找边缘位,如有一个大小为100的数组,第一位是0,最后一位是100,我们使用该方法查找0 100时会发现,我们第一次求出的mid的值就是对应的下标,所以插值法简化了二分查找。

斐波那契查找

斐波那契查找与二分查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,即n=F(k)-1;(n为数组的大小,如果数组大小小于F(k)-1,则我们会将数组填充,即拿数组最后一位填充,直到n大于等于F(k)-1时)

 开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种

 1)相等,mid位置的元素即为所求

 2)key>arr[mid],low=mid+1,k-=2;说明:low=mid+1说明待查找的元素在[mid+1,hign]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))= Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找

 3)  key<,arr[mid],high=mid-1,k-=1;说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归的应用斐波那契查找

    private static int maxSize=20;
    public static void main(String[] args)
    {
        int[] arr={1,8,10,89,1000,1234};
    }
    //因为后面我们的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]的值可能大于数组长度,因此我们需要使用Arrays类,构造一个新的数组,并指向temp
        //不足的部分用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 全部的元素=前面的元素+后面的元素
                //2 f[k]=f[k-1]+f[k-2]
                //因为前面有f[k-1]个元素,所以我们可以继续拆分
                //即在f[k-1]的前面继续查找,即下次循环mid=f[k-1-1]-1
                k--;
            }else if(key>temp[mid])
            {
                //说明我们应该向数组的右边进行查找
                low=mid+1;
                //说明:
                //1 全部的元素=前面的元素+后面的元素
                //2 f[k]=f[k-1]+f[k-2]
                //3 因为后面有f[k-2]个元素
                //即在f[k-2]的前面可以继续进行查找k-=2
                //即下次循环mid=f[k-1-2]-1
                k-=2;
            }else{
                //需要确定返回的是哪个下标
                if(mid<high)
                {
                    return mid;
                }else {
                    return high;
                }
            }
        }
        return -1;
    }

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!