JAVA 数据结构与算法(三)—— 查找算法(顺序/线性查找、二分查找/折半查找、插值查找、斐波那契查找)

一、查找算法

1、顺序/线性查找

(1)介绍

  • 线性查找(Linear Search)又称顺序查找,是一种最简单的查找方法,它的基本思想是从第一个记录开始,逐个比较记录的关键字,直到和给定的K值相等,则查找成功;若比较结果与文件中n个记录的关键字都不等,则查找失败。
  • 查找是对具有相同属性的数据元素(记录)的集合(数据对象)进行的,称之为表或文件,也称字典。
  • 对表的查找,若仅对表进行查找操作,而不能改变表中的数据元素,为静态查找;对表除了进行查找操作外,还可能对表进行插入或删除操作,则为动态查找。

(2)示例

/*线性查找*/
public class LinerSearch {
    public static void main(String[] args) {
        /*定义一个数组*/
        int arr[] = {5,0,7,2,9,4,6,1,8,3};
        /*定义一个要查找的值,如4*/
        int value = 4;

        int index = linerSearch(arr, value);

        if(index == -1){
            System.out.println("该数字中没有要查找的数值");
        }else{
            System.out.println("该数组中下标为" + index + "的数值为要查找的值");
        }
    }

    /*线性查找法:找到一个满足条件的就满足*/
    public static int linerSearch(int[] arr, int value){
        for(int i = 0; i < arr.length; i++){
            if(arr[i] == value){
                return i;
            }
        }
        return -1;
    }
}

---------------------
输出结果为:
该数组中下标为5的数值为要查找的值

2、二分查找/折半查找

(1)介绍
二分查找(Binary earch)也叫折半查找,是一种基本的查找算法,这种查找方法需要待查的表满足两个条件:

  • 查找表必须使用顺序的存储结构。
  • 查找表必须按关键字大小有序排列。

算法的基本思想是:

  • 在有序表中,取中间数据作为比较对象,若给定值与中间记录的关键字相等,则查找成功;
  • 若给定值小于中间记录的关键字,则在中阔记录的左半区继续查找;
  • 若给定值大于中间记录的关键字,则在中间记录的右半区继续查找;
  • 不断重复上述过程,直到查找成功,或所有查找区域元记录,查找失败为止。

(2)示例

/*二分查找*/
public class BinarySearch {
    public static void main(String[] args) {
        /*定义一个数组*/
        int arr[] = {11,22,33,44,55,66,77,88,99,111,122,133,144,155};
        /*定义一个要查找的值,如33*/
        int value = 33;

        int index = binarySearch(arr, value, 0, arr.length - 1);
        if(index == -1){
            System.out.println("该数字中没有要查找的数值");
        }else{
            System.out.println("该数组中下标为" + index + "的数值为要查找的值");
        }
    }

    /*二分查找*/
    public static int binarySearch(int[] arr, int value, int left, int right){
        /*如果left > right,或者value小于最小值或大于最大值则表示没有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            return -1;
        }

        /*left为左下标,right为右下标,mid为中间值下标*/
        int mid = (left + right) / 2;
        /*中间下标对应的值*/
        int midValue = arr[mid];
        
        /*递归查找*/
        if(value < midValue){
            /*向左递归*/
            right = mid - 1;
            return binarySearch(arr,value,left,right);
        }else if(value > midValue){
            /*向右递归*/
            left = mid + 1;
            return binarySearch(arr,value,left,right);
        }else{
            return mid;
        }
    }
}

---------------------
输出结果为:
该数组中下标为2的数值为要查找的值

(3)优化
在上面的示例中,数组中每个值都只有一个,但是实际情况可能会出现多个相同的值,如arr[] = {11,22,33,33,33,33,33,44,55,66,77,88,99,111,122,133,144,155},这时在进行查找的话就必须要把所有相同的值全部查找出来。具体实现方式如下

/*二分查找*/
public class BinarySearch {
    public static void main(String[] args) {
        /*定义一个数组*/
        int arr[] = {11,22,33,33,33,33,33,44,55,66,77,88,99,111,122,133,144,155};
        /*定义一个要查找的值,如33*/
        int value = 33;

        List<Integer> list = binarySearch(arr, value, 0, arr.length - 1);
        if(list.size() == 0){
            System.out.println("该数字中没有要查找的数值");
        }else{
            System.out.println("该数组中要查找的值为:" + list);
        }
    }

	/*二分查找优化*/
    public static List<Integer> binarySearch(int[] arr, int value, int left, int right){
        /*如果left > right,或者value小于最小值或大于最大值则表示没有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            /*没有找到则返回一个空的集合*/
            return new ArrayList<Integer>();
        }

        /*left为左下标,right为右下标,mid为中间值下标*/
        int mid = (left + right) / 2;
        /*中间下标对应的值*/
        int midValue = arr[mid];


        /*递归查找*/
        if(value < midValue){
            /*向左递归*/
            right = mid - 1;
            return binarySearch(arr,value,left,right);
        }else if(value > midValue){
            /*向右递归*/
            left = mid + 1;
            return binarySearch(arr,value,left,right);
        }else{
            /*创建一个集合用于存放查找到的值的下标*/
            List<Integer> indexList = new ArrayList<Integer>();
            indexList.add(mid);
            /*向左查找相同值*/
            int temp = mid - 1;
            while(true){
                if(temp < 0 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp -= 1;
            }
            /*向右查找相同值*/
            temp = mid + 1;
            while(true){
                if(temp > arr.length - 1 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp += 1;
            }
            return indexList;
        }
    }
}

---------------------
输出结果为:
该数组中要查找的值为:[3, 2, 4, 5, 6]

3、插值查找

(1)介绍

  • 插值查找,有序表的一种查找方式。插值查找是根据查找关键字与查找表中最大最小记录关键字比较后的查找方法。
  • 插值查找基于二分查找,将查找点的选择改进为自适应选择,提高查找效率。
  • 插值算法的优缺点:
    • 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快.
    • 关键字分布不均匀的情况下,该方法不一定比折半查找要好

(2)示例

/*插值查找*/
public class InsertSearch {
    public static void main(String[] args) {
        /*定义一个数组*/
        int arr[] = {1,2,3,3,3,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
        /*定义一个要查找的值,如4*/
        int value = 3;
        List<Integer> list = insertSearch(arr, value, 0, arr.length - 1);
        if(list.size() == 0){
            System.out.println("该数字中没有要查找的数值");
        }else{
            System.out.println("该数组中要查找的值为:" + list);
        }
    }

    /*插值查找*/
    public static List<Integer> insertSearch(int[] arr, int value, int left, int right){
        /*如果left > right,或者value小于最小值或大于最大值则表示没有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            return new ArrayList<Integer>();
        }

        /*left为左下标,right为右下标,mid为中间值下标*/
        int mid = left + (right - left) * (value - arr[left]) / (arr[right] - arr[left]);
        int midValue = arr[mid];
        if(value < midValue){
            /*向左递归查找*/
            right = mid - 1;
            return insertSearch(arr, value, left, right);
        }else if(value > midValue){
            /*向左递归查找*/
            left = mid + 1;
            return insertSearch(arr, value, left, right);
        }else{
            /*创建一个集合用于存放查找到的值的下标*/
            List<Integer> indexList = new ArrayList<Integer>();
            indexList.add(mid);
            /*向左查找相同值*/
            int temp = mid - 1;
            while(true){
                if(temp < 0 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp -= 1;
            }
            /*向右查找相同值*/
            temp = mid + 1;
            while(true){
                if(temp > arr.length - 1 || arr[temp] != value){
                    break;
                }
                indexList.add(temp);
                temp += 1;
            }
            return indexList;
        }
    }
}

---------------------
输出结果为:
该数组中要查找的值为:[2, 3, 4, 5]

4、斐波那契查找

(1)介绍

  • 黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割,也称为中外比。这是一个神奇的数字,会带来意想不到的效果。
  • 斐波那契数列{1,1, 2,3,5, 8, 13,21, 34, 55}发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值0.618。
  • 斐波那契搜索(Fibonacci search),又称斐波那契查找,是区间中单峰函数的搜索技术,斐波那契查找是在二分查找的基础上根据斐波那契数列进行分割的。
  • 斐波那契查找与折半查找很相似,他是根据斐波那契序列的特点对有序表进行分割的。他要求开始表中记录的个数为某个斐波那契数小1,及n=F(k)-1;开始将k值与第F(k-1)位置的记录进行比较(及mid=low+F(k-1)-1),比较结果也分为三种:
    • 相等,则mid位置的元素即为所求;
    • 大于>,则low=mid+1,k-=2;
      说明:low=mid+1说明待查找的元素在[mid+1,high]范围内,k-=2 说明范围[mid+1,high]内的元素个数为n-(F(k-1))=Fk-1-F(k-1)=Fk-F(k-1)-1=F(k-2)-1个,所以可以递归的应用斐波那契查找。
    • 小于<,则high=mid-1,k-=1。
      说明:low=mid+1说明待查找的元素在[low,mid-1]范围内,k-=1 说明范围[low,mid-1]内的元素个数为F(k-1)-1个,所以可以递归的应用斐波那契查找。
  • 在最坏情况下,斐波那契查找的时间复杂度还是O(log2n),且其期望复杂度也为O(log2n),但是与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小,但是还是得视具体情况而定。

(2)示例

/*斐波那契查找*/
public class FibonacciSearch {
    public static int SIZE = 20;
    public static void main(String[] args) {
        /*定义一个数组*/
        int arr[] = {1,11,22,33,44,55,66,77,88,99,100};
        /*定义一个要查找的值,如4*/
        int value = 22;

        int index = fibonacciSearch(arr, value);
        if(index == -1){
            System.out.println("该数字中没有要查找的数值");
        }else{
            System.out.println("该数组中下标为" + index + "的数值为要查找的值");
        }
    }

    /*斐波那契查找*/
    public static int fibonacciSearch(int[] arr, int value){
        /*left为左下标,right为右下标,k表示斐波那契分割数值的下标,mid表示存放找到的值*/
        int left = 0;
        int right = arr.length - 1;
        int k = 0;
        int mid = 0;
        /*获取斐波那契数列*/
        int[] fib = fib();

        /*如果left > right,或者value小于最小值或大于最大值则表示没有找到*/
        if(left > right || value < arr[0] || value > arr[arr.length-1]){
            return -1;
        }

        /*获取斐波那契分割数值的下标*/
        while(right > fib[k] - 1){
            k++;
        }

        /*因为fib[k]的值可能大于arr的长度,所以需要新建一个数组并指向arr数组*/
        int[] tempArr = Arrays.copyOf(arr,fib[k]);
        /*使用arr数组最后的值填充新数组tempArr*/
        for(int i = right + 1; i < tempArr.length; i++){
            tempArr[i] = arr[right];
        }

        /*循环查找*/
        while(left <= right){
            mid = left + fib[k - 1] -1;
            if(value < tempArr[mid]){
                /*继续向数组的左边查找*/
                right = mid - 1;
                k -= 1;
            }else if(value > tempArr[mid]){
                /*继续向数组的右边查找*/
                left = mid + 1;
                k -= 2;
            }else{
                if(mid <= right){
                    return mid;
                }
                else{
                    return right;
                }
            }
        }
        return -1;
    }

    /*创建一个斐波那契数列,并生成具体的数值*/
    public static int[] fib(){
        int[] fib = new int[SIZE];
        fib[0] = 1;
        fib[1] = 1;
        for(int i = 2; i < SIZE; i++){
            fib[i] = fib[i - 1] + fib[i - 2];
        }
        return fib;
    }
}

---------------------
输出结果为:
该数组中下标为2的数值为要查找的值
发布了104 篇原创文章 · 获赞 58 · 访问量 7504

猜你喜欢

转载自blog.csdn.net/baidu_27414099/article/details/104420788