Java 实现二分法查找算法

二分查找(binary search),也称折半搜索,是一种在 有序数组 中 查找某一特定元素 的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

Java 实现二分法查找算法_第1张图片

有两种方式:分别为递归实现和循环实现,代码如下:

package com.learn.datastructure.array;

/*
    二分法查找
 */
public class BinarySearch {

    // 查找次数
    static int count;

    public static void main(String[] args) {

        int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        System.out.println(searchRecursive(array, 0, array.length - 1, 9));
        System.out.println(count);

        count = 0;
        System.out.println(searchLoop(array, 9));
        System.out.println(count);

    }

    /**
     * 执行递归二分查找,返回第一次出现该值的位置
     *
     * @param array     已排序的数组
     * @param start     开始位置
     * @param end       结束位置
     * @param findValue 需要找的值
     * @return 值在数组中的位置,从0开始。找不到返回-1
     */
    public static int searchRecursive(int[] array, int start, int end, int findValue) {
        // 如果数组为空,直接返回-1,即查找失败
        if (array == null) {
            return -1;
        }
        count++;
        if (start <= end) {
            // 中间位置
            int middle = start + (end - start) / 2;

            //int middle = (start + end) / 2;(start + end)存在着溢出的风险,进而得到错误的middle结果,导致程序错误。
            // 中值
            int middleValue = array[middle];

            if (findValue == middleValue) {
                // 等于中值直接返回
                return middle;
            } else if (findValue < middleValue) {
                // 小于中值时在中值前面找
                return searchRecursive(array, start, middle - 1, findValue);
            } else {
                // 大于中值在中值后面找
                return searchRecursive(array, middle + 1, end, findValue);
            }
        } else {
            // 返回-1,即查找失败
            return -1;
        }
    }

    /**
     * 循环二分查找,返回第一次出现该值的位置
     *
     * @param array     已排序的数组
     * @param findValue 需要找的值
     * @return 值在数组中的位置,从0开始。找不到返回-1
     */
    public static int searchLoop(int[] array, int findValue) {
        // 如果数组为空,直接返回-1,即查找失败
        if (array == null) {
            return -1;
        }

        // 起始位置
        int start = 0;

        // 结束位置
        int end = array.length - 1;

        while (start <= end) {
            count++;
            // 中间位置
            int middle =start + (end - start) / 2;
            // 中值
            int middleValue = array[middle];

            if (findValue == middleValue) {
                // 等于中值直接返回
                return middle;
            } else if (findValue < middleValue) {
                // 小于中值时在中值前面找
                end = middle - 1;
            } else {
                // 大于中值在中值后面找
                start = middle + 1;
            }
        }
        // 返回-1,即查找失败
        return -1;
    }
}

时间复杂度和空间复杂度分析:

时间复杂度
     最坏情况:查找最后一个元素(或者第一个元素)Master定理T(n)=T(n/2)+O(1)所以T(n)=O(logn)
  最好情况:查找中间元素O(1)查找的元素即为中间元素(奇数长度数列的正中间,偶数长度数列的中间靠左的元素)

咱们需要考虑的是最坏情况。

递归实现的次数和深度都是log2N,所以,时间复杂度:O(log2N),空间复杂度:O(log2N )

循环实现的基本次数是log2N,所以,时间复杂度是O(log2N);由于辅助空间是常数级别的,所以,空间复杂度是O(1)。

二分查找中值的计算

这是一个经典的话题,如何计算二分查找中的中值?大家一般给出了两种计算方法:

  • 算法一:middle = (start + end) / 2;
  • 算法二:middle = start + (end - start) / 2;

乍看起来,算法一简洁,算法二提取之后,跟算法一没有什么区别。但是实际上,区别是存在的。算法一的做法,在极端情况下,(end - start)存在着溢出的风险,进而得到错误的mid结果,导致程序错误。而算法二能够保证计算出来的middle,一定大于start ,小于end,不存在溢出的问题。

二分查找法的缺陷

二分查找法的O(log n)让它成为十分高效的算法。不过它的缺陷却也是那么明显的。就在它的限定之上:必须有序,我们很难保证我们的数组都是有序的。当然可以在构建数组的时候进行排序,可是又落到了第二个瓶颈上:它必须是数组。

数组读取效率是O(1),可是它的插入和删除某个元素的效率却是O(n)。因而导致构建有序数组变成低效的事情。

解决这些缺陷问题更好的方法应该是使用二叉查找树了,最好自然是自平衡二叉查找树了,既能高效的(O(n log n))构建有序元素集合,又能如同二分查找法一样快速(O(log n))的搜寻目标数。

你可能感兴趣的:(Java 实现二分法查找算法)