二分查找算法的升级版

Posted cherish010

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二分查找算法的升级版相关的知识,希望对你有一定的参考价值。

二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。

时间复杂度:O(logn)

一、二分查找容易出错的3个地方:
  1、循环退出条件

    注意是low <= high.

  2、mid的取值

    mid = (low + high)>>2 这种写法有问题,因为如果low和high比较大的话,两者之和就有可能溢出。因为改进的方法mid = low + ((high - low )>>2).

  3、low和high的更新

    low = mid + 1,high  = mid -1.如果直接写成low = mid或者high = mid,就可能发生死循环。

二、二分查找的几种变体的代码实现

  最简单的情况:有序数组中不存在重复元素

  循环:

public static int binarySearch(int[] nums, int value) {
        if(nums == null) {
            return -1;
        }
        int length = nums.length;
        int low = 0;
        int high = length -1;
        while(low <= high) {
            int mid = low + ((high -low)>>2);//为什么不 (low + high)>>2 当low和high 很大时,防止溢出
            if(value == nums[mid]) {
                return mid;
            }else if (value < nums[mid]) {
                high = mid -1;
            }else {
                low = mid + 1;
            }
        }
        return -1;
    }

  递归:

public int bsearch(int[] nums, int value) {
        return binarySearchRecursion(nums, 0, nums.length -1 , value);
    }
    public static int binarySearchRecursion(int[] nums, int start, int end, int x) {
        if(start > end){//空表
            return -1;
        }
        int mid = (start + end)/2;
        if(x == nums[mid]) {
            return mid;
        }else if(x < nums[mid]) {
            return binarySearchRecursion(nums, start, mid - 1, x);
        }else {
            return binarySearchRecursion(nums, mid + 1, end, x);
        }
    }

  变体一:找出第一个等于给定值的元素

public static int binarySearch1(int[] nums, int value) {
        if(nums == null) {
            return -1;
        }
        int length = nums.length;
        int low = 0;
        int high = length -1;
        while (low <= high) {
            int mid  = low + ((high - low)>>2);
            if (value < nums[mid]) {
                high = mid -1;
            }else if(value > nums[mid]) {
                low = mid + 1;
            }else {
                if(mid == 0 || nums[mid - 1] != value) {
                    return mid;
                }
                high = mid -1;
            }
        }
        return -1;
    }

  变体二:找出最后一个等于给定值的元素

public static int binarySearch2(int[] nums, int value) {
        if(nums == null) {
            return -1;
        }
        int length = nums.length;
        int low = 0;
        int high = length - 1;
        while(low <= high) {
            int mid = low + ((high - low)>>2);
            if(value < nums[mid]) {
                high = mid -1;
            }else if(value > nums[mid]) {
                low  = mid + 1;
            }else {
                if(mid == length -1 || nums[mid + 1] != value) {
                    return mid;
                }
                low = mid + 1;
            }
        }
        return -1;
    }

  变体三:查找第一个大于等于给定值的元素

public static int binarySearch3(int[] nums, int value) {
        if(nums == null) {
            return -1;
        }
        int length = nums.length;
        int low = 0;
        int high = length - 1;
        while(low <= high) {
            int mid = low + ((high - low)>>2);
            if(value >= nums[mid]) {
                if(mid == 0 || nums[mid -1] < value) {
                    return mid;
                }
                low = mid + 1;
            }else {
                high = mid -1;
            }
        }
        return -1;
    }
    

  变体四:查找最后一个小于等于给定值的元素

public static int binarySearch4(int[] nums, int value) {
        if(nums == null) {
            return -1;
        }
        int length = nums.length;
        int low = 0;
        int high = length - 1;
        while(low <= high) {
            int mid = low + ((high - low)>>2);
            if(value <= nums[mid]) {
                if(mid == high - 1 || nums[mid + 1] > value) {
                    return mid;
                }
                high = mid - 1;
            }else {
                low = mid + 1;
            }
        }
        return -1;
    }

三、二分查找应用场景的局限性

  1、二分查找依赖的是顺序表结构,简单点就是数组

       二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找不再适用。

  2、二分查找针对的是有序数据

       二分查找算法需要按照下标随机访问元素。下表随机访问数据的时间复杂度是O(1),而链表随机访问的时间复杂度是O(n),所以数据如果使用链表存储,二分查找的时间复杂度就会变得很高。

  3、数据量太小不适合二分查找

    如果数据量很小,完全没有必要二分查找,直接遍历就足够了,但有一个例外,如果数据之间的比较操作非常耗时,不管数据量大小,都推荐用二分查找。比如:数组中存储的都是长度为300的字符串,比较如此长的两个字符    串,非常耗时,我们尽可能减少比较次数,减少比较次数会提高性能,这时二分查找比顺序遍历更有优势。

  4、数据量太大也不适合二分查找

    二分查找底层依赖的是数组这种数据结构,数组存储需要连续的内存空间,对内存的要求比较苛刻。当太大的数据用数组存储比较吃力,就不能用二分查找了。

 

以上是关于二分查找算法的升级版的主要内容,如果未能解决你的问题,请参考以下文章

插值查找

算法之希尔排序快速排序二分查找

PHP实现二分查找算法(代码详解)

python算法:二分查找

「算法笔记」一文摸秃二分查找

二分查找算法讲解及其C++代码实现