每日算法-二分查找

适用场景

适用于有序数组中查找某一个值.
每查找一次,就将搜寻范围缩小一半,
平均时间复杂度是O(log_{2}N), 根据换底公式, 简记作:O(lgN).

主要难点

主要难点在于边界条件的判断;

大致思路:

1.当供查找的数组不合法时,直接返回结果,查询无果;

2.当数组长度等于1时,直接判断是否相等即可;

3.当数组仅有两个元素时,直接分别判断;

4.当数组元素多于2个时, 采用如下策略:

1) 有限比较数组头尾两个元素与查询目标元素的值比较, 因为是有序数组,如果在界外,则直接判断给出查询结果;

2)如果和边界元素比较后,范围在界内,则开始真正的二分查找法,分别采用low, high, mid三个游标来界定查询范围.

实现方式

1.迭代法

2.递归法

3.Arrays.binarySearch(int[] arr, int target) API

实践代码如下:


import java.util.Arrays;

/**
 * 默认有序数组arr[]是从小到大的顺序排列的,如果不是则需要先结合排序算法使用.
 */
public class BinarySearch {

    // 迭代法
    public int binarySearch1(int[] arr, int target) {
        // 先判断数据的有效性
        if (arr == null || arr.length < 1) {
            return -1;
        }

        int low = 0;
        final int length = arr.length;
        int high = length - 1;
        int mid;
        // System.out.println("length:" + length + ",low:" + low + ",high:" + high
        //         + ",mid:" + (low + high) / 2);

        // 先判断头尾游标的值
        if (length == 1) {

            if (arr[0] == target) {
                return 0;
            } else {
                return -1;// not found
            }
        } else { // length >= 2
            if (arr[low] == target) {
                return low;
            } else if (arr[high] == target) {
                return high;
            } else if (arr[low] > target || arr[high] < target) {
                //如果待查找的数值在有序数组最大或者最小值之外,直接判查询未果,无须再二分查找了.
                return -1;
            }
        }

        while (low < high) {
            mid = (low + high) / 2;
            // System.out.println("low:" + low
            //         + ",value[low]:" + arr[low]
            //         + ",high:" + high + ",value[high]:" + arr[high]
            //         + ",mid:" + mid + ",value[mid]:" + arr[mid]);
            //这段非常重要,否则将可能出现死循环,
            //当头游标和尾游标的中间值已经是起始或者末尾两个游标位之一时,代表查找结束,且无果.
            if (high == mid || low == mid) {
                return -1;
            }
            if (arr[mid] < target) {
                low = mid;
            } else if (arr[mid] > target) {
                high = mid;
            } else if (arr[mid] == target) {
                return mid;
            }
        }
        return -1;
    }

    // 递归法
    public int binarySearch2(int[] arr, int target) {
        // System.out.println("enter binarySearch2");
        if (null == arr || arr.length < 1) {
            return -1;
        }

        final int length = arr.length;
        if (length == 1) {
            if (arr[0] == target) {
                return 0;
            } else {
                return -1;// not found
            }
        } else { // length >= 2

            if (arr[0] == target) {
                return 0;
            } else if (arr[arr.length - 1] == target) {
                return arr.length - 1;
            } else if (arr[0] > target || arr[length - 1] < target) {
                return -1;// not found
            }
        }
        return binarySearchRecursion(arr, 0, arr.length - 1, target);
    }

    private int binarySearchRecursion(int arr[], int low, int high, int target) {
        // System.out.println("length:" + arr.length + ",low:" + low + ",high:" + high
        //         + ",mid:" + (low + high) / 2);
        int mid;
        while (low < high) {
            mid = (low + high) / 2;

            //这段非常重要,否则将可能出现死循环,
            //当头游标和尾游标的中间值已经是起始或者末尾两个游标位之一时,代表查找结束,且无果.
            if (mid == low || mid == high) {
                return -1;
            }

            if (arr[mid] == target) {
                return mid;
            } else if (arr[mid] < target) {
                return binarySearchRecursion(arr, mid, high, target);
            } else if (arr[mid] > target) {
                return binarySearchRecursion(arr, low, mid, target);
            }
        }
        return -1;
    }

    // Arrays.binarySearch API法
    public int binarySearchArrays(int[] arr, int target) {
        if (arr == null || arr.length <= 0) {
            return -1;
        }
        //Arrays.sort(arr);
        return Arrays.binarySearch(arr, target);
    }

    public static void main(String[] args) {

        // 多个测试用例数组
        int[] arr = {
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,};

        int[] arr2 = {
            1, 2, 4,};

        int[] arr3 = {
            1, 2, 4, 6
        };

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

        int[] bigArr = new int[10000];
        for (int i = 0; i < 10000; i++) {
            bigArr[i] = i * 2;
        }

        System.out.println("init arr done!");

        int wanted = 6;
        int[] toBeSearchArr = bigArr;

        BinarySearch bs = new BinarySearch();

        System.out.println(
                "binearySearch1 :" + bs.binarySearch1(toBeSearchArr, wanted));

        System.out.println(
                "binearySearch2 recursion :" + bs.binarySearch2(toBeSearchArr, wanted));

        System.out.println("Arrays.binarySearch():" + bs.binarySearchArrays(toBeSearchArr, wanted));
    }
}

测试结果如下:

init arr done!
binearySearch1 :3
binearySearch2 recursion :3
Arrays.binarySearch():3

[Done] exited with code=0 in 1.508 seconds

Arrays.binarySearch API算法实现如下:

根据OpenJDK11源码网站:https://hg.openjdk.org/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/util/Arrays.java

// code from https://hg.openjdk.org/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/util/Arrays.java
    public static int binarySearch(int[] a, int key) {
        return binarySearch0(a, 0, a.length, key);
    }

    public static int binarySearch(int[] a, int fromIndex, int toIndex,
            int key) {
        rangeCheck(a.length, fromIndex, toIndex);
        return binarySearch0(a, fromIndex, toIndex, key);
    }

    // Like public version, but without range checks.
    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];

            //System.out.println("low:" + low + ",high:" + high + ",mid:" + 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.
    }

    /**
     * Checks that {@code fromIndex} and {@code toIndex} are in the range and
     * throws an exception if they aren't.
     */
    static void rangeCheck(int arrayLength, int fromIndex, int toIndex) {
        if (fromIndex > toIndex) {
            throw new IllegalArgumentException(
                    "fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
        }
        if (fromIndex < 0) {
            throw new ArrayIndexOutOfBoundsException(fromIndex);
        }
        if (toIndex > arrayLength) {
            throw new ArrayIndexOutOfBoundsException(toIndex);
        }
    }

从上可知, JDK实现中,除了判断数组的有效性之外,边界并未做特殊处理,举例:

当target/key元素小于第一个元素或者大于最后一个元素时,需要跑log_{2}N(N>3)次才可以得到查询无果的消息, 而上边自写方案做了特殊处理的,直接判断一次即可.

另外还有个差异点是: Arrays的实现当查询无果时,返回的值,并非统一的-1, 而是-(low+1).

1)  小于第一个元素

        int[] bigArr = new int[100000];
        for (int i = 0; i < 100000; i++) {
            bigArr[i] = i * 2;
        }

        System.out.println("init arr done!");

        int wanted = -100002;// 小于第一个元素,Arrays需要查询17次才可以得到结果.
        int[] toBeSearchArr = bigArr;

        BinarySearch bs = new BinarySearch();

        long startTime = System.currentTimeMillis();
        int result = -1;
        //for (int j = 0; j < 1000000; j++) {
        result = bs.binarySearchArrays(toBeSearchArr, wanted);
        //}
        long timeUsed = System.currentTimeMillis() - startTime;

        System.out.println("binarySearchArrays search " + wanted + ",result: " + result + " -> using " + timeUsed + "ms");

        long startTime1 = System.currentTimeMillis();
        int result1;
        //for (int j = 0; j < 1000000; j++) {
        result1 = bs.binarySearch2(toBeSearchArr, wanted);
        //}
        long timeUsed1 = System.currentTimeMillis() - startTime1;

        System.out.println("binarySearch2 search " + wanted + ",result: " + result1 + " -> using " + timeUsed1 + "ms");

输出结果:

2) 大于最后一个元素

        int wanted = 200002;// 7, 6, 8
        int[] toBeSearchArr = bigArr;

输出结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值