八,Java实现简单的查找算法(顺序, 折半, 差值查找)

八, 查找算法

在Java中, 我们常用的查找有四种: 顺序查找, 二分(折半)查找, 插值查找, 斐波那契查找(另作一篇文章)

8.1 顺序查找

  1. 适用于存储结构为顺序或链式存储的线性表
  1. 顺序查找的基本思想:

又称为无序查找, 从数据结构的一端开始, 顺序扫描, 依次将扫描到的结点关键字与给定值k比较, 若相等则查找成功. 若扫描结束仍没找到关键字等于k的结点,表示查找失败.

  1. 性能分析:

时间复杂度为o(n), 查找不成功时需要比较n+1次.
顺序表对表无要求,插入数据可在o(1)内完成, 缺点是时间复杂度较大, 数据规模较大时,效率较低.

  • 算法较简单, 随便举个栗子:
public class sequanceSearch {
    public static void main(String[] args) {
        ///顺序查找
        int target = 12346;
        int arr[] = {1,8,10,89,1000,1234};

        for(int i=0; i<arr.length; i++){
            if(arr[i] == target){
                System.out.println("找到了目标值"+target+", 下标为: "+i);
                return;
            }
        }
        System.out.println("未找到");
    }
}

8.2 二分查找(折半查找)[要求有序]

  1. 待查找元素必须是有序的, 如果是无序则要进行排序操作.
  1. 二分查找的基本思想:

①, 中间结点把线性表分为了两个子表, 首先我们用给定值k先与中间结点的关键字比较, 若相等则查找成功;
②, 若不相等, 再根据k与该中间结点关键字的比较结果确定下一步去查找哪一个子表, 这样递归进行, 直到查找到或查找结果发现无匹配.

  1. 性能分析

最坏情况下, 关键字比较次数为 log2(n+1), 期待时间复杂度为 o(log2n);
优点: 比较次数少, 查找速度快, 平均性能好,占用系统内存较少.
缺点: 要求待查表为有序表, 插入删除困难.
所以折半查找适用于不经常变动但查找较为频繁的有序列表.


  1. 实现思路:

8.2.1 二分查找基本实现

1. 递归法实现二分查找

public class BinarySearch{
    //二分查找递归实现
    public int binarySearch(int[] arr, int findVal, int low, int high){
        // 使用(low+high)/2会有整数溢出的可能
        int mid = low + (high - low)/2;
        int midVal = arr[mid];
        //写成多个if主要是为了更清楚的显示这个方法的逻辑
        //递归出口
        if(low > high) return -1;
        //左递归
        if(midVal > findVal)
            return binarySearch(arr, findVal, 0, mid-1);
        //右递归
        if(midVal < findVal)
            return binarySearch(arr, findVal, mid+1, arr.length-1);
        if(midVal == findVal)
            return mid;
        return -1; //注意我们使用 else if写整个方法会更加简洁, 并且这一句可省略;
    }
}

2. 非递归方式实现二分查找

在这里插入图片描述

package DataStrcture.SearchDemo;

public class NonRecurBinarySearch {
    //二分查找的非递归实现
    public static int binarySearch(int target, int[] arr) {
        int left = 0;
        int right = arr.length - 1;

        while (left <= right) {
            int mid = (left + right) / 2;
            if (arr[mid] == target) return mid;

            if (target < arr[mid]) {
                right = mid - 1;
            }
            if (target > arr[mid]) {
                left = mid + 1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7};
        int tar = binarySearch(1,arr);
        System.out.println("找到了, 索引为"+tar);
    }
}

8.2.2 二分查找优化实现(查找多个相同的数)

实现思路
/**
目标: 对折半查找进行优化, 使其能够查找多个相同的数据,并返回数据个数以及索引
思路:
* 1. 我们需要使用一个list存放查找到的目标数据的索引
* 2. 当我们找到目标值arr[mid]时,先在mid左侧遍历, 找寻相同数据
* 3. 然后mid右侧遍历,同样是为了找寻相同数据
* 4. 注意,我们跳出遍历循环的条件是, 遍历左侧或右侧到了头(<0), 尾部(>arr.length-1), 或者 找到的数据不再等于目标值
* (因为折半查找的数据是有序的, 目标值当然是连续的了,)
*/

  • 具体实现如下:
import java.util.ArrayList;
import java.util.List;

public class UpdatedBinarySearch {
    //测试方法
    public static void main(String[] args) {

        int arr[] = { 1, 8, 10, 89,1000,1000,1000, 1234};
        UpdatedBinarySearch ub = new UpdatedBinarySearch();
        ArrayList<Integer> integers = ub.uBinarySearch(arr, 0, arr.length - 1, 1000);

        System.out.println(integers);
    }
    ///主方法
    public ArrayList<Integer> uBinarySearch(int[] arr, int low, int high, int findVal) {
        if (low > high) return new ArrayList<>();//递归出口

        int mid = low + (high - low) / 2;
        int midVal = arr[mid];
        ArrayList<Integer> list = new ArrayList<Integer>();

            //左边递归
            if (midVal > findVal) {
                return uBinarySearch(arr, 0, mid - 1, findVal);
            }
            //右边递归
            else if (midVal < findVal) {
                return uBinarySearch(arr, mid + 1, high, findVal);
            }
            // 找到了(arr[mid] == findVal)
            else{
                //左边查找相同数据
                int temp = mid - 1;
                while (true) {
                    if (temp < 0 || arr[temp] != midVal) break;
                    list.add(temp);
                    temp -= 1;
                }
                list.add(mid);
                //右边查找相同数据
                int temp_1 = mid + 1;
                while (true) {
                    if (temp_1 > arr.length - 1 || arr[temp_1] != midVal) break;
                    list.add(temp_1);
                    temp_1 += 1;
                }
            }
            return list;
    }
}

8.3 插值查找[要求有序]

  1. 基于二分查找算法, 将查找点的选择改进为自适应选择, 可以提高效率. 相应的, 插值查找也属于有序查找
  2. 性能分析:
    查找成功或失败的时间复杂度均为: o(log2(log2n))
    插值查找适用于表长较大, 而关键字分布又比较均匀的查找表, 这种情况下的平均性能比折半好的多, 如果分布不均匀, 则插值不太合适.

插值查找的核心: mid值是自适应的

mid值的计算方法:

8.3.1 插值查找的递归实现

注: 插值查找除了mid是自适应的,需要通过固定的计算公式得出意外, 其他的实现跟二分查找基本一致, 但是插值查找在待查序列分布比较均匀(比如序列从小到大或从大到小排列)的查找速度相当快!

  • 代码实现:
package DataStrcture.SearchDemo;

import java.util.Arrays;

public class InsertSearch {
    public static void main(String[] args) {
        int[] arr= new int[100];
        for(int i=0; i < 100; i++){
            arr[i] = i+1;
        }
        int findVal = 99;
        int index = insertSearch(arr,findVal, 0, arr.length-1);
        System.out.println("index = "+index);
    }
	//插值查找的主方法
    public static int insertSearch(int[] arr, int findVal, int low, int high){
        System.out.println("hi");
        //递归出口
        if(low > high || findVal < arr[0] || findVal > arr[arr.length-1]) return -1;
        
        /// 核心: mid///
        int mid  = low + (high - low)*(findVal - arr[low])/(arr[high] - arr[low]);
        int midVal = arr[mid];

        if(midVal > findVal){
            return insertSearch(arr, findVal, low, mid-1);
        }else if(midVal < findVal){
            return insertSearch(arr, findVal, mid+1, high);
        }else{
            return mid;
        }
    }
}
posted @ 2022-05-26 20:31  青松城  阅读(275)  评论(0编辑  收藏  举报