二分查找法详细解说(Java版)

​二分查找法,也被称为折半查找法,是一种在有序数组中查找某一特定元素的搜索算法,它是一种速率较高的查找算法。

算法思想:搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。

时间复杂度:

  • 最好情况下:只需要进行一次比较就能找到目标元素  O(1);

  • 最坏时间复杂度:O(log2^n)

  • 平均时间复杂度:O(log2^n)

下面是如何用文字描述二分查找法的步骤:

初始化两个指针,一个指向数组的开始(low),一个指向数组的结束(high)。计算中间元素的索引 mid = (low + high) >>> 2(使用整数除法)。检查中间元素是否是要查找的元素。如果是,返回 mid。如果要查找的元素小于中间元素,更新 high = mid - 1,    并在数组的左半部分重复步骤 2 和 3。如果要查找的元素大于中间元素,更新 low = mid + 1,    并在数组的右半部分重复步骤 2 和 3。如果 low > high,说明元素不在数组中,返回 -1 或其他表示未找到的值。

注:图中的最后的25弄错了,应该是35;

代码实现:

public class BinarySearch01 {
    public static void main(String[] args) {
        int[] array = {1,3,4,6,8,10,24,26,27,30,35};
        int target = 26;
        int result = myBinarySearch(array, target);
        System.out.println(result);
    }
​
    private static int myBinarySearch(int[] array, int target) {
        //计算数组长度
        int len = array.length;
        //左边指针
        int low= 0;
        //右边指针
        int high= len - 1;
        while (low <= high) {
            //中间变量
            //注:>>>无符号右移,忽略符号位,空位都以0补齐,避免用 / 造成失误
            int mid = (low+ high) >>> 1;
            //目标值小于中间值
            if (target < array[mid]) {
                //右指针赋值为中间值减一
                high= mid - 1;
            } else if (target > array[mid]) {
                //左指针赋值为中间值加一
                low= mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
}


的到这里来,看看java为我们提供的是如何实现的吧(Arrays中):

private static int binarySearch0(int[] a, int fromIndex, int toIndex,
                                     int key) {
        int low = fromIndex;
        int high = toIndex - 1;
​
        while (low <= high) {
            int mid = (low + high) >>> 1;
            int midVal = a[mid];
​
            if (midVal < key)
                low = mid + 1;
            else if (midVal > key)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found.
    }

看着是不是和上面的差不多呀,不同点在于查找不到元素的返回值不一样,我们的是直接返回-1,而官方返回的是-(low + 1)。

让我们看看官方的解释:

大概意思就是:使用二分查找算法在指定的数组中搜索指定的值。在进行此调用之前,必须对数组进行排序(如 sort(short[]) 方法)。如果未对其进行排序,则结果未定义。如果数组包含多个具有指定值的元素,则无法保证会找到哪一个元素。

    形参:a – 要搜索的数组 key – 要搜索的值 返回值:搜索键的索引,如果它包含在数组中;否则,(-(插入点) - 1)。插入点定义为将键插入到数组中的点:第一个元素的索引大于键,如果数组中的所有元素都小于指定的键,则为 a. 长度。请注意,这保证了当且仅当找到键时,返回值将为 >= 0。

脑袋是不是还没有反应回来,简单点说,和我们想的一样,就是返回值不一样,它表示“负的插入点减一”,至于为什么是负的,感兴趣的小伙伴可以去查查看。

注:(1)循环条件一定是low>=high,千万不要写错了;

问题:(1)要是循环条件写成low>high,会如何?

  • low和high同时指向的元素不会被参与比较。

这个比较简单,这里就不详细解释了,小伙伴们可以画图试试看;

         (2)计算中间值为什么用“>>>”,而不是“/”?大家看看下面的代码就知道了。

public class test {
    public static void main(String[] args) {
        int low = 0;
        int high = Integer.MAX_VALUE - 1;//int类型的最大值减一
        int mid = (low+high)/2;
        System.out.println(mid);
        low = mid +1;
        mid = (low+high)/2;
        System.out.println(mid);
    }
}


 

大家发现问题了吗,但在现实中,一般遇不到这么大的数。

看到这里有的小伙伴应该会问,我看到的有些和你的不一样呀???

为了加深大家对二分查找法的印象,给大家提供改动版本;

public class BinarySearch02 {
    public static void main(String[] args) {
        int[] array = {1,3,4,6,8,10,24,26,27,30,35};
        int target = 26;
        int result = myBinarySearch02(array, target);
        System.out.println(result);
    }
​
    private static int myBinarySearch02(int[] array, int target) {
        //计算数组长度
        //第一处不一样
        int len = array.length;
        //左边指针
        int low= 0;
        //右边指针
        int high= len;
        //第二处不一样
        while (low< high) {
            //中间变量
            //注:>>>无符号右移,忽略符号位,空位都以0补齐,避免用 / 造成失误
            int mid = (low+ high) >>> 1;
            //目标值小于中间值
            if (target < array[mid]) {
                //第三处不一样
                high= mid;
            } else if (target > array[mid]) {
                //左指针赋值为中间值加一
                low= mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
​
}

总体来说,和基础版的没有什么区别,就三处不同而已,

看看图解:

总的来说,和第一种没有多大的区别!

放大招了,有的小伙伴看见了这两种方法肯定发现了,里面的if语句有点繁琐,以你们的聪明才智肯定找到了解决方案,看看我的,和你们的一样不:

public class BinarySearch03 {
    public static void main(String[] args) {
        int[] array = {1,3,4,6,8,10,24,26,27,30,35};
        int target = 26;
        int result = myBinarySearch03(array, target);
        System.out.println(result);
    }
​
    private static int myBinarySearch03(int[] array, int target) {
        //左边指针
        int left = 0;
        //右边指针
        int right = array.length;
        //循环条件为:左指针和右指针的元素为零
        //思想:不在while循环里面找出,循环的作用为不断缩小范围直到只剩left指向的元素时结束循环,
        //     然后再循环外面比较array[left] 是否等于 target ;是就返回 left ,不是就返回-1;
        while (right - left > 1) {
            int mid = (left + right) >>> 1;
            if (target >= array[mid]) {
                left = mid;
            } else {
                right = mid;
            }
        }
        if (array[left] == target) {
            return left;
        } else {
            return -1;
        }
    }
}

算法思路:不在while循环里面找出,循环的作用为不断缩小范围直到只剩low指向的元素时结束循环,然后再循环外面比较array[left] 是否等于 target ;是就返回 low,不是就返回-1;

小伙伴们肯定遇到过一哥有序数组里面有重复的元素,要求找最左边或者最右边的那个元素。下面Diudiu给大家展示一下吧!

核心思想

  1. 先找到第一个等于给定值的元素

  2. 然后向左遍历,直到找到第一个等于给定值的元素

时间复杂度:O(logn)

空间复杂度:O(1)

其他的和基础版的一样,无非就是比基础版多了一点记录的步骤!

下面是代码是实现:

是不是发现if语句里面有的是重复的,看起来有点繁琐,强迫症的小伙伴肯定受不了了,下面是合并过的代码:

public class BinarySearchLeftMost02 {
    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 6, 7, 9, 9, 9, 9, 9, 11, 24, 56, 66, 77, 88, 99, 121, 234, 345};
        int target = 346;
        int result = myBinarySearchLeftMost02(array, target);
        System.out.println(result);
    }
​
    private static int myBinarySearchLeftMost02(int[] array, int target) {
        //计算数组长度
        int len = array.length - 1;
        //左边指针
        int low= 0;
        //右边指针
        int high= len;
        //零时变量记录最开始查找到的值
        while (low<= high) {
            //中间变量
            //注:>>>无符号右移,忽略符号位,空位都以0补齐,避免用 / 造成失误
            int mid = (low+ high) >>> 1;
            //目标值小于中间值
            if (target <= array[mid]) {
                //右指针赋值为中间值减一
                high= mid - 1;
            } else {
                //左指针赋值为中间值加一
                low= mid + 1;
            }
        }
        //找到就返回该元素索引,找不到就返回目标元素插入点
        return low;
    }
​
}

有的小伙伴可能想不通为什么找不到元素要返回low,看注释就说找不到就返回该元素的插入点,这是怎么来的?看看下面的图,大家就懂了!

看完这张图之后,大家发现没有,low和mid(也就是temp)位置是一样的,要是以为是巧合的或者想看找不到的情况的,再看看下面这张:

看到这里,大家明白了吗,同样的道理,右边的也是这样:

第一种方法,找到就返回,找不到就返回-1:

 

第二种方法:找到就返回,找不到就返回所在位置:

public class BinarySearchRightMost02 {
    public static void main(String[] args) {
        int[] array = {1, 2, 3, 4, 6, 7, 9, 9, 9, 9, 9, 11, 24, 56, 66, 77, 88, 99, 121, 234, 345};
        int target = 346;
        int result = myBinarySearchRightMost02(array, target);
        System.out.println(result);
    }
​
    private static int myBinarySearchRightMost02(int[] array, int target) {
        //计算数组长度
        int len = array.length - 1;
        //左边指针
        int low= 0;
        //右边指针
        int high= len;
        while (low<= high) {
            //中间变量
            //注:>>>无符号右移,忽略符号位,空位都以0补齐,避免用 / 造成失误
            int mid = (low+ high) >>> 1;
            //目标值小于中间值
            if (target < array[mid]) {
                //右指针赋值为中间值减一
                high= mid - 1;
            } else {
                //左指针赋值为中间值加一
                low= mid + 1;
            }
        }
        //找到就返回该元素索引,找不到就返回目标元素插入点
        return high;
    }
​
}

好了,到这里二分查找法的基本方法就写完了,小伙伴们做做下面的题目巩固一下吧!

温馨提示:上看完上面的再看题目哟,先自己完成,想不通就在看一遍文章,然后再看看我这个吧,最后去力扣找到对应的题目完成。

例题1(力扣704):

public class BinarySearch01 {
    public static void main(String[] args) {
        int[] array = {-1,0,3,5,9,12};
        int target = 9;
        int result = myBinarySearch01(array, target);
        System.out.println(result);
    }
    private static int myBinarySearch01(int[] array, int target) {
        //计算数组长度
        int len = array.length;
        //左边指针
        int left = 0;
        //右边指针
        int right = len - 1;
        while (left <= right) {
            //中间变量
            //注:>>>无符号右移,忽略符号位,空位都以0补齐,避免用 / 造成失误
            int mid = (left + right) >>> 1;
            //目标值小于中间值
            if (target < array[mid]) {
                //右指针赋值为中间值减一
                right = mid - 1;
            } else if (target > array[mid]) {
                //左指针赋值为中间值加一
                left = mid + 1;
            } else {
                return mid;
            }
        }
        return -1;
    }
}

例题2(力扣35):看见这个题目是不是很熟悉呢

public class BinarySearchLeftMost02 {
    public static void main(String[] args) {
        int[] array = {1,3,5,6};
        int target = 5;
        int result = myBinarySearchLeftMost02(array, target);
        System.out.println(result);
    }
​
    private static int myBinarySearchLeftMost02(int[] array, int target) {
        //计算数组长度
        int len = array.length - 1;
        //左边指针
        int left = 0;
        //右边指针
        int right = len;
        //零时变量记录最开始查找到的值
        while (left <= right) {
            //中间变量
            //注:>>>无符号右移,忽略符号位,空位都以0补齐,避免用 / 造成失误
            int mid = (left + right) >>> 1;
            //目标值小于中间值
            if (target <= array[mid]) {
                //右指针赋值为中间值减一
                right = mid - 1;
            } else {
                //左指针赋值为中间值加一
                left = mid + 1;
            }
        }
        //找到就返回该元素索引,找不到就返回目标元素插入点
        return left;
    }
​
}
​

看完之后是不是觉得和上面的一样呢?

例题3(力扣34):

由于题目是力扣里面的,我们不能直接运行,就以示例1展示吧:

class Solution {
    public static void main(String[] args) {
        // 定义一个整数数组array,并赋值
        int[] array = {5,7,7,8,8,10};
        // 定义一个整数target,并赋值
        int target = 8;
​
        // 调用myBinarySearch方法,传入array和target,并将结果赋值给result
        int[] result = myBinarySearch(array, target);
        // 遍历result,并输出结果
        System.out.print("[");
        for (int i = 0; i < result.length; i++) {
            if (i != result.length - 1) {
                System.out.print(result[i] +",");
            }else {
                System.out.println(result[i] +"]");
            }
        }
​
    }
    // 定义一个方法myBinarySearch,传入一个整数数组array和一个整数target,返回一个整数数组result
    public static int[] myBinarySearch(int[] array, int target) {
        // 定义一个整数数组result,长度为2
        int[] result = new int[2];
        // 定义一个整数low,初始值为0
        int low = 0;
        // 定义一个整数high,初始值为array的长度减1
        int high = array.length - 1;
        // 定义一个整数tempLeft,初始值为-1
        int tempLeft = -1;
        // 使用while循环,条件为low小于等于high
        while(low<=high) {
​
            // 计算mid的值,mid为(low+high)除以2
            int mid = (low + high) >>> 1;
            // 如果target小于array[mid],则high等于mid减1
            if (target < array[mid]) {
                high = mid - 1;
            // 如果target大于array[mid],则low等于mid加1
            } else if (target > array[mid]) {
                low = mid + 1;
            // 如果target等于array[mid],则tempLeft等于mid,high等于mid减1
            } else {
                tempLeft = mid;
                high = mid - 1;
            }
            // 将tempLeft的值赋值给result[0]
            result[0] = tempLeft;
        }
        // 定义一个整数left,初始值为0
        int left = 0;
        // 定义一个整数right,初始值为array的长度减1
        int right = array.length - 1;
        // 定义一个整数tempRight,初始值为-1
        int tempRight = -1;
        // 使用while循环,条件为left小于等于right
        while(left<=right) {
​
            // 计算mid的值,mid为(left+right)除以2
            int mid = (left + right) >>> 1;
            // 如果target小于array[mid],则right等于mid减1
            if (target < array[mid]) {
                right = mid - 1;
            // 如果target大于array[mid],则left等于mid加1
            } else if (target > array[mid]) {
                left = mid + 1;
            // 如果target等于array[mid],则tempRight等于mid,left等于mid加1
            } else {
                tempRight = mid;
                left = mid + 1;
            }
            // 将tempRight的值赋值给result[1]
            result[1] = tempRight;
​
        }
        // 返回result
        return result;
    }
}

注:上述代码由本人完成,有错误的勿怪!!!想看相关视频解说,请打开哔哩哔哩大学,搜索CodeDiudiu( https://b23.tv/W3S9VRk)!!!

总结:

  1. 二分查找是一个时间效率极高的算法,时间复杂度是log (n)。

  2. 二分法的套路包括确定一个区间和找到一个满足二段性的性质。

  3. 二分查找的应用包括查找目标值区域的左边界、右边界,查找最小元素等。

更多算法相关问题请关注

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值