Bootstrap

Java查找算法——(二)二分查找(完整详解,附有案例+代码)

1.1 概述

也叫折半查找

每次排除一半的查找范围

前提条件:元素必须是有序的,从小到大,或者从大到小都是可以的。

如果是无序的,也可以先进行排序。但是排序之后,会改变原有数据的顺序,查找出来元素位置跟原来的元素可能是不一样的,所以排序之后再查找只能判断当前数据是否在容器当中,返回的索引无实际的意义。

基本思想:也称为是折半查找,属于有序查找算法。用给定值先与中间结点比较。

优缺点:

  • 优点是比较次数少,查找速度快,平均性能好;
  • 其缺点是要求待查表为有序表,且插入删除困难。
  • 因此,折半查找方法适用于不经常变动而查找频繁的有序列表。

1.2 基本版实现

public class Test01 {
    public static void main(String[] args) {
       // 基础版:二分查找算法

        int[] arr = { 1,2, 6,66};
        int i = binarySearch(arr, 6);
        System.out.println(i);//1

    }
    // 定义方法,查找目标数字的索引,、
    // 找到返回对应的索引
    // 没有找到返回-1
    public static int binarySearch(int[] arr, int target){
        // 起始索引
        int start = 0;
        // 末尾索引
        int end = arr.length-1;

        // start索引和end索范围内有东西执行查找
        while (start <= end){
            // 定义中间索引
            int mid = (start +end)/2;
            // int mid = (start +end)>>>1;
            // 目标函数 小于 中间值
            if (target <arr[mid]){
                end = mid - 1;
            }else if (arr[mid] < target){
                start = mid + 1;
            }else {
                return mid;
            }
        }
        return -1;
    }
    // BinarySearch()  结束
}

//假设binarySearch()方法中的while循环执行了k次
//则 while中的if 分别比较了多少次?
//当查找的值在数组最左侧,共需要比较k次
//当查找的值在数组最右侧,共需要比较2k次。
//所以比较不平衡,平衡版见1.4
  • 问题1: 为什么是 i<=j 意味着区间内有未比较的元素, 而不是 i<j ?

    i==j 意味着 i,j 它们指向的元素也会参与比较
    i<j 只意味着 m 指向的元素参与比较

  • 问题2: (start + end) / 2 有没有问题?

    如果 int end = Integer.MAX_VALUE-1;在运行(start + end)会出现内存溢出。

    同一个二进制数:1011-111-1111-1111-1111-1111-1111-1110

    不把高位视为符号位,代表:3221225470

    把最高位视为符号位,代表:-1073741826

    解决办法:

    采用右移一位,右移一位的效果相当于除以2向下取整。

    只需将 int mid = (start + end) / 2 改成 int mid = (start + end) >>> 1

1.3 变动版

public class Test02 {
    public static void main(String[] args) {
       // 变动版:二分查找算法

        int[] arr = {1,2, 6,66};
        int i = binarySearch(arr, 6);
        System.out.println(i);//1


    }
    // 定义方法,查找目标数字的索引,、
    // 找到返回对应的索引
    // 没有找到返回-1
    public static int binarySearch(int[] arr, int target){
        // 起始索引
        int start = 0;
        // 末尾索引;
        // 第一处变动,end指针不参与运算,只作为边界
        int end = arr.length;

        // start索引和end索范围内有东西执行查找
        // 第二处变动 <= 变为 <
        while (start < end){
            // 定义中间索引
            // int mid = (start +end)/2;
            int mid = (start +end) >>> 1;
            // 目标函数 小于 中间值
            if (target <arr[mid] ){
                //第三变动,
                end = mid;
            }else if (arr[mid] < target){
                start = mid + 1;
            }else {
                return mid;
            }
        }
        return -1;
    }
    // BinarySearch()  结束
}

递归实现二分查找

public class Test03 {
    public static void main(String[] args) {
// 递归实现二分查找
        int[] arr = {1,2,3,66};

        int i = binarySearch(arr, 3, 0, arr.length-1);
        System.out.println(i);//2

    }
    /**
     * 使用递归的二分查找
     *title:recursionBinarySearch
     *@param arr 有序数组
     *@param key 待查找关键字
     *@return 找到的位置
     */
    public static int binarySearch(int[] arr,int key,int low,int high){

        if(key < arr[low] || key > arr[high] || low > high){
            return -1;
        }

        int middle = (low + high) >>>1 ;			//初始中间位置
        if(arr[middle] > key){
            //比关键字大则关键字在左区域
            return binarySearch(arr, key, low, middle - 1);
        }else if(arr[middle] < key){
            //比关键字小则关键字在右区域
            return binarySearch(arr, key, middle + 1, high);
        }else {
            return middle;
        }

    }
}

1.4 平衡版

public class Test04 {
    public static void main(String[] args) {
        // 平衡版二叉查找;
        int[] arr = {1,2,3,66};
        int i = binarySearch(arr, 66);
        System.out.println(i);//3


    }
    public static int binarySearch(int[] arr,int target){

        int start = 0;
        // end:不参与运算,只作为边界
        int end = arr.length;
      
        // end - start:表示该范围内待查找的元素个数
        //等范围内只剩start时退出循环,
        // 在循环外比较arr[start]与target
        //将原来的if三分支改为二分支,循环内的平均比较次数减少了
        while (1 < end - start){
            int mid = (start + end) >>>1;
            if (target < arr[start]){
                end = mid;
            }else {
                start = mid;
            }
        }
        // 在循环外比较arr[start]与target
        if (arr[start] == target){
            return start;
        }else {
            return -1;
        }
    }
}

1.5 Java中二分查找

Java中Arrays类中的二分查找

public class Test05 {
    public static void main(String[] args) {
        /*
        [2,5,8]    arr
        [2,0,0,0]  b
        [2,4,0,0]  b
        [2,4,5,8]  b

         */

      int[] arr = {2,5,8};
      int target = 4 ;
      int i = binarySearch(arr, target);
      System.out.println(i);//-2


        if (i < 0){
            // target的插入点
            int insertIndex = Math.abs(i + 1);
            // 定义新的数组
            int[] b = new int[arr.length + 1];
            // 将旧数组插入点之前的数赋值到新数组
            System.arraycopy(
                    arr,//旧数组
                    0,//从旧数组的0索引开始拷贝
                    b,//拷贝到b数组
                    0,//拷贝b数组的0索引
                    //将旧数组的 insertIndex-0 范围内拷贝到b数组
                    insertIndex-0
            );//此时b数组为 [2,0,0,0]

            // 将目标值插入b数组,此时b 数组为[2,4,0,0]
            b[insertIndex]  =  target;

            // 将旧数组中插入点以及插入点之后的数复制到新数组b中
            System.arraycopy(
                    arr,
                    insertIndex,
                    b,
                    insertIndex+1,
                    arr.length-insertIndex
            );
            System.out.println(Arrays.toString(b));
            //[2, 4, 5, 8]
        }

        //Java中Arrays类中的二分查找
        int i1 = Arrays.binarySearch(arr, 4);
        System.out.println(i1);//-2


    }
    public static int binarySearch(int[] arr, int target){
        // 起始索引
        int start = 0;
        // 末尾索引;
        // 第一处变动,end指针不参与运算,只作为边界
        int end = arr.length;

        // start索引和end索范围内有东西执行查找
        // 第二处变动
        while (start < end){
            // 定义中间索引
            // int mid = (start +end)/2;
            int mid = (start +end) >>> 1;
            // 目标函数 小于 中间值
            if (target <arr[mid] ){
                //第三变动
                end = mid;
            }else if (arr[mid] < target){
                start = mid + 1;
            }else {
                return mid;
            }
        }
      	 //返回(-(插入点) - 1)
        return -(start + 1 );
    }
    // BinarySearch()  结束
}

1.6 二分查找查最左边元素

假如数组有多个相同的值,返回目标值最左边的元素索引

public class Test06 {
    public static void main(String[] args) {

        int[] arr ={1,2,2,2,6,6,6,6,8};

        int i = binarySearchLeftmost(arr, 2);
        System.out.println(i);//1


    }

    public static int binarySearchLeftmost(int[] arr, int target){
        // 起始索引
        int start = 0;
        // 末尾索引
        int end = arr.length-1;

        // 记录待查找元素的候选位置
        int candidate = -1;

        // start索引和end索范围内有东西执行查找
        while (start <= end){
            // 定义中间索引
            int mid = (start +end)>>>1;
            // 目标函数 小于 中间值
            if (target <arr[mid] ){
                end = mid - 1;
            }else if (arr[mid] < target){
                start = mid + 1;
            }else {
                // 记录候选位置
                candidate = mid;
                // 向左缩小范围
                end = mid-1;
            }
        }
       //循环结束返回最左边的查找目标值的候选位置
        return candidate;
    }
    // BinarySearch()  结束

}

查找最最左边时,改进返回值

public class Test08 {
    public static void main(String[] args) {
        // 假如数组有多个相同的值,返回目标值最左边的元素索引
        int[] arr ={1,2,2,2,6,6,6,6,8};

        int i = binarySearchLeftmost(arr, 2);
        System.out.println(i);//1

        int j = binarySearchLeftmost(arr, 3);
        System.out.println(j);//4

    }
    public static int binarySearchLeftmost(int[] arr, int target){
        // 起始索引
        int start = 0;
        // 末尾索引
        int end = arr.length-1;

        // start索引和end索范围内有东西执行查找
        while (start <= end){
            // 定义中间索引
            int mid = (start +end)>>>1;
            // 目标函数 小于 中间值
            if (target <= arr[mid] ){
                end = mid - 1;
            }else {
                start = mid + 1;
            }
        }
     //循环结束,假如找到在数组中找到目标值则返回最左边的查找目标值索引
     //如果没有找到返回的是目标值要插入的位置。
     //总的来说返回的是 >= target的最靠左索引
        return start;
    }
    // BinarySearch()  结束
}

1.7 二分查找最右边元素

假如有多个相同的值,返回最右边的元素索引,没有找到返回-1

public class Test07 {
    public static void main(String[] args) {

        int[] arr ={1,2,2,2,6,6,6,6,8};

        int i = binarySearchLeftmost(arr, 2);
        System.out.println(i);//3

    }

    public static int binarySearchLeftmost(int[] arr, int target){
        // 起始索引
        int start = 0;
        // 末尾索引
        int end = arr.length-1;

        // 记录待查找元素的候选位置
        int candidate = -1;

        // start索引和end索范围内有东西执行查找
        while (start <= end){
            // 定义中间索引
            int mid = (start +end)>>>1;
            // 目标函数 小于 中间值
            if (target <arr[mid] ){
                end = mid - 1;
            }else if (arr[mid] < target){
                start = mid + 1;
            }else {
                // 记录候选位置
                candidate = mid;
                // 向左缩小范围
                start = mid + 1;
            }
        }
        //循环结束返回最右边的查找目标值的候选位置
        return candidate;
    }
    // BinarySearch()  结束

}

查找最最右边时,改进返回值

public class Test09 {
    public static void main(String[] args) {
        // 假如数组有多个相同的值,返回目标值最右边的元素索引
        int[] arr ={1,2,2,2,6,6,6,6,8};

        int i = binarySearchLeftmost(arr, 2);
        System.out.println(i);//3

        int j = binarySearchLeftmost(arr, 3);
        System.out.println(j);//3

    }
    public static int binarySearchLeftmost(int[] arr, int target){
        // 起始索引
        int start = 0;
        // 末尾索引
        int end = arr.length-1;

        // start索引和end索范围内有东西执行查找
        while (start <= end){
            // 定义中间索引
            int mid = (start +end)>>>1;
            // 目标函数 小于 中间值
            if (target < arr[mid] ){
                end = mid - 1;
            }else {
                start = mid + 1;
            }
        }
        // 返回 <= target 的最靠右的索引
        return start - 1;
    }
    // BinarySearch()  结束
}

;