查找算法

1. 线性查找

public static int seqSearch(int[] nums, int target) {
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == target) {
                return i;
            }
        }
        return -1;
    }

2. 二分查找

  • 二分查找通常用于查找单调数组中的某一值,但是扩展的二分查找可以查找单调函数的某一值(将循环体内的nums[]换成function()即可)
  • 核心:搜索区间都为闭,while里面带等号,mid必须加减1,while结束返回-1;

2.1 迭代二分查找

/**
     * 迭代二分查找(最常用)
     *
     * @param nums     输入数组
     * @param target   要查找的值
     * @return         找到查找值返回对应索引,没有则返回-1
     */
    public static int iterativeBinarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        int mid = 0;

        if (target < nums[left] || target > nums[right]) {
            return -1;
        }

        while (left <= right) {
            mid = left + ((right - left) >> 1);
            if (nums[mid] == target) {
                return mid;
            }else if (nums[mid] > target) {
                right = mid - 1;
            }else if (nums[mid] < target) {
                left = mid + 1;
            }
        }
        //没找到返回-1
        return -1;
    }

2.2 递归二分查找

/**
     * 递归二分查找
     *
     * @param nums      输入数组
     * @param target    要查找的值
     * @param left      数组左边的索引
     * @param right     数组右边的索引
     * @return          找到查找值返回对应索引,没有则返回-1
     */
    public static int recursionBinarySearch(int[] nums,int target,int left,int right){
        if(target < nums[left] || target > nums[right] || left > right){
            return -1;
        }

        int mid = left + ((right - left) >> 1);
        if(nums[mid] > target){
            //比关键字大则关键字在左区域
            return recursionBinarySearch(nums, target, left, mid - 1);
        }else if(nums[mid] < target){
            //比关键字小则关键字在右区域
            return recursionBinarySearch(nums, target, mid + 1, right);
        }else {
            return mid;
        }
    }

2.3 左侧二分查找

  • 核心:搜索区间左闭右开,while用小于号,if相等相反边界(左侧搜索改right,右侧搜索改left),加1放在左边界,右边界直接等mid,while结束返回左边界,右侧搜索返左边界减1!
/**
     * 找到目标值得左边界
     * 对于 nums = [2,3,5,7], target = 1,返回结果 0。含义是:nums 中小于 target 的元素有 0 个。
     * 对于 nums = [2,3,5,7], target = 8,返回结果 4。含义是:nums 中小于 8 的元素有 4 个。
     *
     * @param nums       输入数组
     * @param target    要查找的值
     * @return          找到目标值的左边界
     */
    public static int leftBinarySearch(int[] nums,int target){
        int left= 0;
        int right = nums.length;  // 与基础二分查找的区别!
        int mid = 0;

        while(left < right){  // 与基础二分查找的区别!
            mid = left + ((right - left) >> 1);
            if(nums[mid] == target){
                right = mid;  // 找到 target 时不要立即返回,而是缩小「搜索区间」的上界 right,在区间 [left, mid) 中继续搜索,
            }else if(nums[mid] > target){
                right = mid;  // 与基础二分查找的区别!「搜索区间」是 [left, right)
            }else if (nums[mid] < target) {
                left = mid + 1;
            }
        }
        return left; // while 终止的条件是 left == right,返回哪个都一样
    }

2.4 右侧二分查找

/**
     * 找到目标值的右侧边界,同上面一样,搜索区间都是左闭右开
     *
     * @param arr       输入数组
     * @param target    要查找的值
     * @return          找到目标值的右边界
     */
    public static int rightBinarySearch(int[] arr,int target){
        int left = 0;
        int right = arr.length;
        int mid = 0;

        while(left < right){
            mid = left + ((right - left) >> 1);
            if(arr[mid] == target){
                left = mid+ 1;  // 与左侧二分查找的区别!
            }else if(arr[mid] > target){
                right = mid;
            }else if (arr[mid] < target) {
                left = mid + 1;
            }
        }
        return left-1;  // 与左侧二分查找的区别!
    }

2.5 左右侧边界合并终极版(个人还是倾向分开记忆)

public int boundBinarySearch(int[] nums, int target) {
	    int left = 0;
	    int right = nums.length - 1;
	    int mid = 0;
	    
	    while (left <= right) {
	        mid = left + (right - left) / 2;
	        if (nums[mid] < target) {
	            left = mid + 1;
	        } else if (nums[mid] > target) {
	            right = mid - 1;
	        } else if (nums[mid] == target) {
	            // 别返回,锁定左侧边界
	            right = mid - 1;
	        }
	    }
	    // 左侧:最后要检查 left 越界的情况
	    //if (left >= nums.length || nums[left] != target) {
	        //return -1;
	    //}
	    //return left;
	    
	    // 右侧:最后要检查 right 越界的情况
	    //if (right < 0 || nums[right] != target) {
	        //return -1;
	    //}
	    //return right;
	}

2 插值查找

  • 插值查找是在二分查找的基础上将mid值的计算方法做改进。其mid的计算是自适应计算。
  • 注意:插值查找适用关键字分布比较均匀。对于关键字分布不均匀的情况下效果可能不如二分查找
  • java 分类搜索 java 搜索算法_搜索改为:java 分类搜索 java 搜索算法_二分查找_02
// 二分查找
	int mid = left + ((right - left) >> 1);
	// 插值查找
	int mid = left + (right-left) * (findVal - arr[left]) / (arr[right] - arr[left]);

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

  • 斐波那契数列:{1,1,2,3,5,8,13,21,34,55…}
  • 斐波那契数列性质:java 分类搜索 java 搜索算法_算法_03
  • 斐波那契查找原理类似与二分查找。主要是改变mid值,mid位于黄金分割点附近,即 java 分类搜索 java 搜索算法_java 分类搜索_04。其中F代表斐波那契数列。

    java 分类搜索 java 搜索算法_搜索_05
  1. F[k]-1=F[k-1]-1+F[k-1]-1+1,其中末尾的1代表mid的位置。该式子说明顺序表的长度为F[k-1]-1,纳闷这个顺序表永远都可分为F[k-1]-1和F[k-2]-1两段。故mid=low+F[k-1]-1;
  2. 但是顺序表的长度n不一定刚好等于F[k]-1,所以需要将原来循序表长度n增加到F[k]-1的长度。故F[k]-1>n即可。
/**
     * 定义斐波那契额数组
     * @param arrLength 斐波那契数组长度
     * @return  斐波那契数组
     */
    public static int[] fibonacciArr(int arrLength) {
        int[] fibArr = new int[arrLength];
        fibArr[0] = 1;
        fibArr[1] = 1;
        for (int i = 2; i < arrLength; i++) {
            fibArr[i] = fibArr[i - 1] + fibArr[i - 2];
        }
        return fibArr;
    }
    
	public static int fibonacciSearch(int[] arr, int findVal) {
        int left = 0;
        int right = arr.length - 1;
        int mid = 0;
        // 斐波那契分割下标
        int fibK = 0;
        // 默认斐波那契数组长度为20
        int[] fibArr = fibonacciArr(20);
        
        // 获取斐波那契分割数值的下标
        while (right > fibArr[fibK] - 1) {
            fibK++;
        }

        // fibArr[k] 值可能大于arr的长度,因此我们需要使用Arrays类构造一个新的数组,并指向temp[]
        int[] temp = Arrays.copyOf(arr, fibArr[fibK]);
        // 实际上需求使用arr数组最后的数填充temp
        for (int i = right + 1; i < temp.length; i++) {
            temp[i] = arr[right];
        }

        // 使用while循环处理,找到findVal
        while (left <= right) {
            mid = left + fibArr[fibK - 1] - 1;
            if (findVal < temp[mid]) {
                // 查找值在左侧
                right = mid - 1;
                // 对应F[k-1]-1,所以是自减一
                fibK--;
            } else if (findVal > temp[mid]) {
                // 查找值在右侧
                left = mid + 1;
                // 对应F[k-2]-1,所以是自减二
                fibK -= 2;
            } else {
                if (mid <= right) {
                    return mid;
                } else {
                    return right;
                }
            }
        }
        return -1;
    }