【问题标题】:Binary search bounds二分搜索范围
【发布时间】:2015-08-28 14:46:22
【问题描述】:

我总是在这方面遇到困难,我还没有看到对据说如此普遍和高度使用的东西的明确解释。

我们已经知道标准的二分搜索。给定起始下限和上限,在 (lower + Higher)/2 处找到中间点,然后将其与您的数组进行比较,然后相应地重新设置边界,等等。

但是,调整搜索以查找所需的差异是什么(对于升序的列表):

  1. 最小值 >= 目标
  2. 最小值>目标
  3. 最大值
  4. 最大值

似乎每种情况都需要对算法进行非常小的调整,但我永远无法让它们正常工作。我尝试改变不等式、返回条件,我改变了边界的更新方式,但似乎没有什么是一致的。

这四种情况的确定方法是什么?

【问题讨论】:

  • 我不明白这个问题; 'Smallest value' 和 'Largest value' 分别是数组最左边和最右边位置的值吗?
  • 这是作业题吗?否则,这似乎是一件很奇怪的事情。
  • @StilesCrisis 一点也不。这只是我一次又一次遇到的问题。
  • @Codor 是的,对不起。假设数组按升序排序。
  • 你是问如果目标不在数组中它是如何工作的......?

标签: algorithm binary-search


【解决方案1】:

在我发现循环不变量和谓词是解决所有二元问题的最佳和最一致的方法之前,我遇到了完全相同的问题。

第 1 点:考虑谓词
一般来说,对于所有这 4 种情况(以及对等式的正常二分搜索),将它们想象为谓词。所以这意味着一些值满足谓词而一些失败。因此,例如考虑这个目标为 5 的数组: [1、2、3、4、6、7、8]。找到第一个大于 5 的数字基本上等同于在这个数组中找到第一个:[0, 0, 0, 0, 1, 1, 1]。

第 2 点:包含搜索边界
我喜欢两端总是包容的。但我可以看到有些人喜欢开始包容并结束排他性(在 len 而不是 len -1 上)。我喜欢将所有元素都包含在数组中,所以当提到 a[mid] 时,我认为这不会让我的数组越界。所以我的偏好是:包容!!!

第 3 点:While 循环条件
所以我们甚至想在while循环中处理大小为1的子数组,当while循环结束时应该没有未处理的元素。我真的很喜欢这个逻辑。它总是坚如磐石。最初没有检查所有元素,基本上它们是未知的。这意味着不检查 [st = 0, to end = len - 1] 范围内的所有内容。然后当 while 循环结束时,未检查元素的范围应该是大小为 0 的数组!

第 4 点:循环不变量
由于我们定义了 start = 0, end = len - 1,所以不变量将是这样的: start 剩下的任何东西都小于 target。 任何结束的权利都大于或等于目标。

第 5 点:答案
循环完成后,基本上基于循环不变量,开始左侧的任何内容都较小。所以这意味着 start 是大于或等于目标的第一个元素。 等效地,结束右侧的任何内容都大于或等于目标。所以这意味着答案也等于 end + 1。

代码:

public int find(int a[], int target){
  int start = 0; 
  int end = a.length - 1; 
  while (start <= end){
    int mid = (start + end) / 2; // or for no overflow start + (end - start) / 2
    if (a[mid] < target) 
       start = mid + 1; 
    else // a[mid] >= target
       end = mid - 1; 
  }
  return start; // or end + 1;
}

变体:

这相当于找到第一个 0。所以基本上只返回更改。

return end; // or return start - 1; 

>
将 if 条件更改为 。没有其他变化。


同>,return end; // or return start - 1;

因此,对于所有 5 种变体(、>=、正常二分搜索),一般来说,只有 if 中的条件和 return 语句中的条件发生了变化。当您考虑不变量(第 4 点)和答案(第 5 点)时,计算这些小变化非常容易。

希望这对阅读本文的人有所帮助。如果有什么不清楚的感觉就像魔术,请联系我解释。了解了这个方法之后,二分查找的一切应该就一清二楚了!

额外点:最好也尝试包括开头但不包括结尾。所以数组最初是 [0, len)。如果您可以编写不变量、while 循环的新条件、答案以及清晰的代码,则意味着您了解了这个概念。

【讨论】:

    【解决方案2】:

    二分搜索(至少我实现它的方式)依赖于一个简单的属性——谓词在区间的一端成立,而对另一端不成立。我总是认为我的间隔在一端关闭,在另一端打开。那么我们来看看这段代码sn-p:

    int beg = 0; // pred(beg) should hold true
    int end = n;// length of an array or a value that is guranteed to be out of the interval that we are interested in
    
    while (end - beg >  1) {
      int mid = (end + beg) / 2;
      if (pred(a[mid])) {
        beg = mid;
      } else { 
        end = mid;
      }
    }
    // answer is at a[beg]
    

    这适用于您定义的任何比较。只需将pred 替换为&lt;=target&gt;=target&lt;target&gt;target

    循环退出后,a[beg] 将是给定不等式成立的最后一个元素。

    所以让我们假设(就像在 cmets 中建议的那样)我们想要找到 a[i] &lt;= target 的最大数字。那么如果我们使用谓词a[i] &lt;= target,代码将如下所示:

    int beg = 0; // pred(beg) should hold true
    int end = n;// length of an array or a value that is guranteed to be out of the interval that we are interested in
    while (end - beg >  1) {
      int mid = (end + beg) / 2;
      if (a[mid] <= target) {
        beg = mid;
      } else { 
        end = mid;
      }
    }
    

    循环退出后,您要搜索的索引将是beg

    还取决于比较,您可能必须从数组的右端开始。例如。如果您正在搜索最大值 >= 目标,您将执行以下操作:

    beg = -1;
    end = n - 1;
    while (end - beg >  1) {
      int mid = (end + beg) / 2;
      if (a[mid] >= target) {
        end = mid;
      } else { 
        beg = mid;
      }
    }
    

    您正在搜索的值将使用索引end。请注意,在这种情况下,我考虑了间隔 (beg, end],因此我稍微修改了起始间隔。

    【讨论】:

    • 你能用一个示例谓词来详细说明你的意思吗?
    • 好吧,我建议在最后一句中使用示例。例如,如果您将pred(a[mid]) 替换为a[mid] &lt;= target,您将找到最大的数字&lt;= target。我的回答中的重点是——始终朝这个方向思考——在区间的一端是正确的,而在另一端则不是。您想找到“真相”改变其价值的点。
    • 也许我误解了你的意思,但我试过了,但似乎没有用。例如,如果我想要第一个/最小值 >= 某个目标,我的谓词将是:if a[mid]>=target, beg = mid, else end = mid, 对吗?
    • 我认为我之前的评论有点误导。实际上,在这种情况下,您需要第一个值,它的 `val 不成立,因此对于 begend 不成立的谓词实际上是 @987654340 @ 这是你应该使用的。
    • 所以谓词一定是假的?不是有几种方法可以做到这一点吗?我认为这可能是一个强有力的答案,但我根本不明白这种方法
    【解决方案3】:

    您需要的是让您在最后一步参与该过程的二分搜索。典型的二分搜索将接收(array, element) 并产生一个值(通常是索引或未找到)。但是,如果您有一个修改后的二进制文件,它接受在搜索结束时调用的函数,您可以涵盖所有情况。

    比如在Javascript中为了方便测试,如下二分查找

    function binarySearch(array, el, fn) {
        function aux(left,  right) {
            if (left > right) {
                return fn(array, null, left, right);
            }
    
            var middle = Math.floor((left + right) / 2);
            var value = array[middle];
    
            if (value > el) {
                return aux(left, middle - 1);
            } if (value < el) {
                return aux(middle + 1, right);
            } else {
                return fn(array, middle, left, right);
            }
        }
    
        return aux(0, array.length - 1);
    }
    

    将允许您使用特定的返回函数覆盖每种情况。

    • 默认
      function(a, m) { return m; }
    • 最小值>=目标
      function(a, m, l, r) { return m != null ? a[m] : r + 1 &gt;= a.length ? null : a[r + 1]; }
    • 最小值>目标
      function(a, m, l, r) { return (m || r) + 1 &gt;= a.length ? null : a[(m || r) + 1]; }
    • 最大值function(a, m, l, r) { return m != null ? a[m] : l - 1 &gt; 0 ? a[l - 1] : null; }
    • 最大值function(a, m, l, r) { return (m || l) - 1 &lt; 0 ? null : a[(m || l) - 1]; }

    【讨论】:

      【解决方案4】:

      基本的二分搜索是搜索与目标键相等的位置/值。虽然它可以扩展到找到满足某些条件的最小位置/值,或找到满足某些条件的最大位置/值 em>

      假设数组是升序,如果没有找到满意的位置/值,返回-1。

      代码示例:

        // find the minimal position which satisfy some condition
        private static int getMinPosition(int[] arr, int target) {
            int l = 0, r = arr.length - 1;
            int ans = -1;
            while(l <= r) {
                int m = (l + r) >> 1;
                // feel free to replace the condition
                // here it means find the minimal position that the element not smaller than target
                if(arr[m] >= target) {
                    ans = m;
                    r = m - 1;
                } else {
                    l = m + 1;
                }
            }
            return ans;
        }
      
        // find the maximal position which satisfy some condition
        private static int getMaxPosition(int[] arr, int target) {
            int l = 0, r = arr.length - 1;
            int ans = -1;
            while(l <= r) {
                int m = (l + r) >> 1;
                // feel free to replace the condition
                // here it means find the maximal position that the element less than target
                if(arr[m] < target) {
                    ans = m;
                    l = m + 1;
                } else {
                    r = m - 1;
                }
            }
            return ans;
        }
      
          int[] a = {3, 5, 5, 7, 10, 15};
          System.out.println(BinarySearchTool.getMinPosition(a, 5));
          System.out.println(BinarySearchTool.getMinPosition(a, 6));
          System.out.println(BinarySearchTool.getMaxPosition(a, 8));
      

      【讨论】:

      • @user4992519 你能粘贴你的测试用例吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-11
      • 2015-09-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-24
      • 2019-08-12
      相关资源
      最近更新 更多