二分查找相关算法

Posted keep求索

tags:

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

上一节我讲了二分查找的原理,并且介绍了最简单的一种二分查找的代码实现:

    //简单二分查找
    public static int bsearchSimple(int[] a, int n, int value){
        int low = 0;
        int high = n-1;

        while (low <= high){
            int mid = low + ((high - low) >> 1);
            if(a[mid] == value){
                return mid;
            } else if(a[mid] < value) {
                low = mid + 1;
            } else {
                high = mid -1;
            }
        }
        return -1;
    }

今天我们来讲几种二分查找的变形问题。

不知道你有没有听过这样一个说法:“十个二分九个错”。二分查找虽然原理极其简单,但是想要写出没有 Bug 的二分查找并不容易。

唐纳德·克努特(Donald E.Knuth)在《计算机程序设计艺术》的第 3 卷《排序和查找》中说到:“尽管第一个二分查找算法于 1946 年出现,然而第一个完全正确的二分查找算法实现直到 1962 年才出现。”

你可能会说,我们上一节学的二分查找的代码实现并不难写啊。那是因为上一节讲的只是二分查找中最简单的一种情况,在不存在重复元素的有序数组中,查找值等于给定值的元素。最简单的二分查找写起来确实不难,但是,二分查找的变形问题就没那么好写了。

二分查找的变形问题很多,主要有以下几个。

今天的内容,我都以数据是从小到大排列为前提,如果你要处理的数据是从大到小排列的,解决思路也是一样的。

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

    //查找第一个值等于给定值的数字
    public static int bsearchFistEqual(int[] a, int n , int value){
        int low = 0;
        int high = n-1;

        while (low <= high){
            int mid = low + ((high - low) >> 1);

            if(a[mid] < value){
                low = mid +1;
            } else if (a[mid] > value){
                high = mid -1;
            } else {
                if(mid == 0 || a[mid -1] != value){
                    return mid;
                } else {
                    high = mid -1;
                }
            }
        }
        return -1;
    }

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

    //查找最后一个值等于给定值的数字
    public static int bsearchLastEqual(int[] a, int n , int value){
        int low = 0;
        int high = n-1;

        while (low <= high){
            int mid = low + ((high - low) >> 1);

            if(a[mid] < value){
                low = mid +1;
            } else if (a[mid] > value){
                high = mid -1;
            } else {
                if(mid == n-1 || a[mid + 1] != value){
                    return mid;
                } else {
                    low = mid + 1;
                }
            }
        }
        return -1;
    }

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

    //查找第一个大于等于给定值的数字
    public static int bsearchFistBigger(int[] a, int n , int value){
        int low = 0;
        int high = n-1;

        while (low <= high){
            int mid = low + ((high - low) >> 1);

            if(a[mid] < value){
                low = mid +1;
            } else if (a[mid] >= value){
                if(mid == 0 || a[mid - 1] < value){
                    return mid;
                }
                high = mid -1;
            }
        }
        return -1;
    }

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

    //查找最后一个小于等于给定值的数字
    public static int bsearchLastSmaller(int[] a, int n , int value){
        int low = 0;
        int high = n-1;

        while (low <= high){
            int mid = low + ((high - low) >> 1);

            if(a[mid] <= value){
                if(mid == n -1 || a[mid + 1] > value){
                    return mid;
                }
                low = mid +1;
            } else if (a[mid] > value){
                high = mid -1;
            }
        }
        return -1;
    }

二分查找更适合用在“近似”查找问题,在这类问题上,二分查找的优势更加明显。比如今天讲的这几种变体问题,用其他数据结构,比如散列表、二叉树,就比较难实现了。

变体的二分查找算法写起来非常烧脑,很容易因为细节处理不好而产生 Bug,这些容易出错的细节有:终止条件、区间上下界更新方法、返回值选择。

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

二分查找算法

算法题16 二分查找及相关题目

二分查找算法及相关题目

《糊涂算法》4.二分查找相关问题——实战leetcode

算法和数据结构解析:3 - 二分查找相关问题

算法和数据结构解析:3 - 二分查找相关问题