zoukankan      html  css  js  c++  java
  • 二分搜索个人小结

    二分搜索是平时用到很多的东西,但是经常在细节上的地方会出现问题,这里我想说说我的一点理解。

    平时最多的情况就是查询一个数出现在有序数组里的位置,那么

    在查找 num 的时候,一般需要用到四种情况:
    1、找得到 num 返回最左边的与其相等的下标.(YES_LEFT)
    2、找得到 num 返回最右边的与其相等的下标.(YES_RIGHT)
    3、找不到 num 返回最接近它的比它小的数(在 num 的左边).(NO_LEFT)
    4、找不到 num 返回最接近它的比它大的数(在 num 的右边).(NO_RIGHT)

    这是从 http://blog.csdn.net/int64ago/article/details/7425727 学到的

    一般我用的二分搜索是这样的:

    1 int num[];
    2 int binary_search(int l, int r, int val)
    3 {
    4     while(l <= r) {
    5         int mid = (l + r) >> 1;
    6         //这里是判定条件: 让区间二分
    7     }
    8     return (l 或者 r);
    9 }

    其中,这个判定条件还有return语句都是一些很细节的东西,但是决定了这个二分搜索是不是正确的.(精髓所在)

    现在有这样一个有序数组[1, 3, 4, 4, 4, 8, 10],一共有7个数.(为了方便数组下标从 1 开始算)

    现在随便写一个判定:

    NO.1:

    1 if(num[mid] < val) l = mid + 1;
    2 else r = mid - 1;

    这句话就是如果 val 比 num[mid] 大, 那么 l 就变成 mid + 1, 否则 r 变成 mid - 1.(这是一句废话)

    分析一下执行完这个判定之后的情况(分为要查的 val 会不会出现在 num[] 中两种情况):

    (1)(不出现的情况): 查的数 val = 2.

    第一次: l = 1, r = 7, mid = 4. num[4] = 4 >= val.执行 else 语句.

    第二次: l = 1, r = 3, mid = 2, num[2] = 3 >= val.执行 else 语句.

    第三次: l = 1, r = 1, mid = 1, num[1] = 1 < val.执行 if 语句.

    结束循环: l = 2, r = 1.

    这个时候, num[l] = 3, 刚好大于 val 的最左边的数, num[r] = 1, 刚好小于 val 的最右边的数.

    所以,我们假设如果是找不到 val 的话,如果 return l,那么返回 NO_RIGHT 的情况, 如果 return r,那么返回 NO_LEFT 的情况.

    单单通过这样一个样例是没法证明这个假设的正确性的,那么接下来我们仔细想想:

    l 指针和 r 指针经过不断移动后,最终一定会移动到一个重合的位置,即 l == r 的情况,这个时候指向的数, 一定是恰好大于 val 或者恰好小于 val 的.这个时候再通过一次判断来进行调整,如果指向的数已经是大于 val 了,那么执行 else 语句, r 左移,这个时候 l 指向已经是恰好大于 val 的数,r 指向的数也就恰好是小于 val 的数了.如果指向已经是小于 val 了,那么执行 if 语句, l 右移,也还是上面那样的结果.

    因此这个假设是成立的.

    (2)(出现的情况): 查的数 val = 4.

    第一次: l = 1, r = 7, mid = 4, num[4] = 4 >= val.执行 else 语句.

    第二次: l = 1, r = 3, mid = 2, num[2] = 3 < val.执行 if 语句.

    第三次: l = 3, r = 3, mid = 3, num[3] = 4 >= val.执行 else 语句.

    结束循环: l = 3, r = 2.

    这个时候我们看到, num[l] = num[3] = 4, 刚好是找得到 val 并且最左边的位置, num[r] = num[2] = 3 != val, 无意义.

    假设如果 return l, 那么返回的是一个指向的数等于 val 并且是最左边的下标.即 YES_LEFT 的情况.

    继续验证一下:

    和(1)类似,如果 num[mid] >= val 的话, r 会往左移动, 否则 l 右移, 最终一定会移动到一个重合的位置 l == r, 使得 num[l] == num[r]恰好等于 val 并且是在最左边,或者是一个恰好小于 val 的最大数.通过最后一次判定调整之后, 如果这个时候已经恰好等于 val 并且是在最左边,那么会执行 else 语句, r 左移, 否则执行 if 语句, l 右移, 因为序列中存在一个数等于 val, 移动之前的数又是刚好小于 val 的最大的数,这个时候移动完的 l 刚好就是等于 val 了,并且是在最左边.

    所以 return l,就可以找到 YES_LEFT的情况了.

    通过NO.1的代码,四种情况,已经有三种情况可以判定了,还剩下一种 YES_RIGHT 的情况.因此还有下面这样的代码:

    NO.2:

    1 if(num[mid] <= val) l = mid + 1;
    2 else r = mid - 1;

    其实和 NO.1 相比就是把等于提到 if 语句了.

    还是分为序列中出现 val 和不出现 val 的情况.

    (1)(不出现的情况): 这种情况和上面 NO.1 是一样的.故不作讨论了.

    (2)(出现的情况): 继续查 val = 4.

    第一次: l = 1, r = 7, mid = 4, num[4] = 4 <= val.执行 if 语句.

    第二次: l = 5, r = 7, mid = 6, num[6] = 8 > val.执行 else 语句.

    第三次: l = 5, r = 5, mid = 5, num[5] = 4 <= val.执行 if 语句.

    结束循环: l = 6, r = 5.

    这个时候, num[l] = 8, num[r] = 4.如果这个时候返回 r ,那么和我们想要的 YES_RIGHT 情况就是一样的.

    因为如果 num[mid] <= val 的话, l 会右移, 否则 r 左移, 最终也还是一定移动到一个重合的位置 l == r, 使得 num[l] == num[r],并且这个 num[l] 是刚好大于 val 的或者恰好是等于 val 并且在最右边.再通过一次判定调整,如果这个时候已经恰好等于 val,那么肯定执行 if 语句, l 右移,否则执行 else 语句, r 左移,得到 r 指向的数刚好就是等于 val 的最右边的数了.

    综合上面的 NO.1 和 NO.2 代码和 两种 return 语句.

    可以将 YES_RIGHT 和 NO_LEFT 这两种归结成 NO.2 代码:

    1 int Y_R_N_L(int l, int r, int val)
    2 {
    3     while(l <= r) {
    4         int m = (l + r) >> 1;
    5         if(num[m] <= val) l = m + 1;
    6         else r = m - 1;
    7     }
    8     return r;
    9 }

    可以将 NO_RIGHT 和 YES_LEFT 这两种归结成 NO.1 代码:

    1 int N_R_Y_L(int l, int r, int val)
    2 {
    3     while(l <= r) {
    4         int m = (l + r) >> 1;
    5         if(num[m] < val) l = m + 1;
    6         else r = m - 1;
    7     }
    8     return l;
    9 }
  • 相关阅读:
    Effective Java 第三版——72. 赞成使用标准异常
    Effective Java 第三版——71. 避免不必要地使用检查异常
    Effective Java 第三版——70. 对可恢复条件使用检查异常,对编程错误使用运行时异常
    Effective Java 第三版——69. 仅在发生异常的条件下使用异常
    Effective Java 第三版——68. 遵守普遍接受的命名约定
    Effective Java 第三版——67. 明智谨慎地进行优化
    Effective Java 第三版——66. 明智谨慎地使用本地方法
    Effective Java 第三版——65. 接口优于反射
    Effective Java 第三版——64. 通过对象的接口引用对象
    Effective Java 第三版——63. 注意字符串连接的性能
  • 原文地址:https://www.cnblogs.com/fightfordream/p/6033165.html
Copyright © 2011-2022 走看看