• 算法之二分法及其应用


    算法之二分法及其应用

    算法思路

    ① 将数组中间元素与目标元素进行比较,如果正好是目标元素,则结束搜索

    ② 如果目标元素大于中间元素,则进入中间元素的右边区域进行查找,重复步骤 ① 的操作

    ③ 如果目标元素小于中间元素,则进入中间元素的左边区域进行查找,重复步骤 ① 的操作

    依次类推,若某一步查找区域为空,则表明没有找到目标元素

    代码实现

        public int binarySearch(int[] arr, int key) {
            int low = 0;
            int high = arr.length - 1;
            while (low <= high) {
                int mid = low + (high - low) / 2; 
                if (key == arr[mid]) {
                    return mid;
                } else if (key > arr[mid]) {
                    low = mid + 1;
                } else {
                    high = mid - 1;
                }
            }
            return -1;  // 表明没有找到对应的值
        }
    

    提醒:这里求 mid 的时候不推荐使用 mid = ( low + high ) / 2 的方式,因为当数组较大的时候,low + high 可能会发生溢出

    推荐写成 mid = low + (high - low) / 2

    ( 除 2 等同于右移一位,所以也可以写成 mid = low + ((high - low) >> 1),右移一位比除 2 的速度要快)

    我曾思考过既然要防止溢出,意味着也可以写成 mid = low / 2 + high / 2 ,不过这种方式算出来没有上述代码中所写方式更精确,譬如 low 为 1,high 为 3, 那么通过这种方式得到的就是 1/2 + 3/2 = 0 + 1 = 1, 而 1 和 3 的中间数应该是 2

    复杂度分析

    通过比较中间值,每次都可以过滤掉一半的元素,所以时间复杂度为 O ( log N )

    应用

    1. 找到等于 num 的值 [前提:有序]

    这是最常见的应用,代码如上所示

    2. 找到大于等于 num 最左侧的位置 [前提:有序]

    题目

    举个栗子,比如 int[] arr = {1, 2, 3, 3, 4, 4, 7, 7, 9} , 那么大于等于 3 的最左侧位置下标为 2,大于等于 5 的最左侧位置下标为 6

    思路

    ① 将数组中间元素与 num 进行比较

    A. 如果中间元素满足 >= num , 则将此下标保存到变量 t 中,并继续中间元素左侧的二分

    B. 如果中间元素不满足 >= num , 则继续中间元素右侧的二分

    按照 ① 步骤不断二分直到二分结束

    代码

       public int findLeftest(int[] arr, int num) {
            int low = 0;
            int high = arr.length - 1;
            int t = -1;
            while (low <= high) {
                int mid = low + (high - low) / 2;
                if (arr[mid] >= num) {
                    t = mid;
                    high = mid - 1;
                } else {
                    low = mid + 1;
                }
            }
            return t;
        }
    

    总结

    找到等于 num 的位置和找到大于等于 num 最左侧的位置最大的不同在于,前者只要找到目标元素的位置,就可以返回;而后者必须要二分结束,才能返回

    相似应用

    找到小于等于 num 最右侧的位置,实现思路和上面是差不多的,有兴趣的同学可以自己试着写一下

    3. 局部最小值问题

    题目

    在数组中找到一个局部最小的位置
    定义局部最小的概念。arr 长度为1时,arr[0] 是局部最小。arr 的长度为 N(N>1) 时,如果 arr[0] < arr[1],那么 arr[O] 是局部最小;如果 arr[N-1] < arr[N-2] ,那么 arr[N-1] 是局部最小;如果 O<i<N-1 ,既有 arr[i] < arr[i-1] ,又有 arr[i] < arr[i+1] ,那么 arr[i] 是局部最小。给定无序数组 arr,已知 arr 中任意两个相邻的数都不相等。写一个函数,只需返回 arr 中任意一个局部最小出现的位置即可。

    思路

    A. 先判断 arr[0] 是否为局部最小,是则返回

    B. 看最后一位是否为局部最小,是则返回

    C. 若都不是,那么在 0 ~ N-1 之间必存在局部最小

    说明如下:由于 arr[0] 和 arr[n-1] 都不是局部最小,那么 arr[0] > arr[1] ,arr[N-1] > arr[N-2], 数组呈现的趋势如下:

    图片说明1

    那么在这数组中,一定存在局部最小值(无论中间的趋势如何,一定会出现一个波谷,因为最左边是呈下降趋势,最右边呈上升趋势,其中就至少存在一个位置,使得趋势发生变化,而发生变化的这个点就是波谷,即我们的局部最小)

    D. 取中间值 M, 判断 M 是否为局部最小,若是则返回;否则,arr[M] > arr[M-1] 或 arr[M] > arr[M+1],假设arr[M] > arr[M-1],由假设情况可得到如下图

    图片说明 2

    E. 那么 0 ~ M 之间必定存在局部最小,依次类推... 必能找到局部最小值

    代码

      public int findMiniNum(int[] arr) {
            int length;
            if (arr == null || (length = arr.length) < 1) {
                return -1;  // 表示不存在
            }
            // 判断 arr[0] 是否为局部最小
            if (length == 1 || arr[0] < arr[1]) {
                return 0;
            }
            // 判断最后一个元素是否为局部最小
            if (arr[length - 1] < arr[length - 2]) {
                return length - 1;
            }
            int low = 1;
            int high = length - 2;
            while (low < high) {
                int mid = low + (high - low) / 2;
                if (arr[mid] > arr[mid - 1]) {
                    high = mid - 1;
                } else if (arr[mid] > arr[mid + 1]) {
                    low = mid + 1;
                } else {
                    return mid;
                }
            }
            return low;	// 此时 low == high,而必定存在局部最小,所以直接返回 low 或 high 即可
        }
    

    总结

    我们惯性思维是二分法只能用于有序数组,这道题打破了我们的常规印象

    欢迎大家来我博客逛逛 mmimo技术小栈

  • 相关阅读:
    JavaScript高级程序设计学习笔记--变量、作用域和内存问题
    JavaScript高级程序设计学习笔记--基本概念
    Silverlight 动画性能
    Silverlight动画之 Animation Easing
    Silverlight 动画详解
    WCF重写ServiceHost,实现独立配置文件
    WP8 双击返回键退出
    Asp.Net之自定义表达式构造器(ExpressionBuilder)
    通过configSource提高web.config配置灵活性
    C# 操作IIS方法集合
  • 原文地址:https://www.cnblogs.com/mmimo/p/15388017.html
Copyright © 2020-2023  润新知