【问题标题】:Difference between basic binary search for upper bound and lower bound?上限和下限的基本二进制搜索之间的区别?
【发布时间】:2015-02-08 00:15:03
【问题描述】:

在文章http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binarySearch中,作者讨论了二分查找。他区分了找到某事为真的最低值和某事为假的最高值。 正在搜索的数组看起来像:

假假假真真

我很好奇为什么这两种情况不同。为什么不能只求最小值为真,然后减一求最大值为假?

Edit2:好的,所以我理解下限和上限。现在,我很难理解,在搜索大于或等于查询的最小整数时,为什么我们不能只将 if(mid>query) 更改为 if(mid>=query) 并让它下限而不是上限。

编辑:这是文章所说的:

“现在我们终于得到了实现二进制搜索的代码,如本节和上一节所述:

binary_search(lo, hi, p):
   while lo < hi:
      mid = lo + (hi-lo)/2
      if p(mid) == true:
         hi = mid
      else:
         lo = mid+1

   if p(lo) == false:
      complain                // p(x) is false for all x in S!

   return lo         // lo is the least x for which p(x) is true

...

如果我们想找到 p(x) 为假的最后一个 x,我们会设计(使用与上述类似的原理)类似的东西:

binary_search(lo, hi, p):
   while lo < hi:
      mid = lo + (hi-lo+1)/2    // note: division truncates
      if p(mid) == true:
         hi = mid-1
      else:
         lo = mid

   if p(lo) == true:
      complain                // p(x) is true for all x in S!

   return lo         // lo is the greatest x for which p(x) is false

。”

【问题讨论】:

  • 好吧,我假设二进制搜索意味着集合看起来像 false .... false true ... true 无论如何
  • 我所指的文章暗示,如果我们正在执行二进制搜索,情况就是如此;我相信这也是二进制搜索甚至适用于这种情况的必要条件。
  • @DietmarKühl 当然可以,但是您不能像if(lo==0&amp;&amp;works(lo)==true)return false 这样轻松检查吗?

标签: c++ binary-search lower-bound upperbound


【解决方案1】:

二分查找的下限和上限是可以在不破坏顺序的情况下插入值的最低和最高位置。 (在 C++ 标准库中,这些边界将由引用可以插入值的元素的迭代器来表示,但这个概念本质上并没有改变。)

以排序范围为例

1 2 3 4 5 5 5 6 7 9

在对3 的二分搜索中,我们将得到

   v-- lower bound
1 2 3 4 5 5 5 6 7 9
     ^-- upper bound

并且在二分查找5:

       v-- lower bound
1 2 3 4 5 5 5 6 7 9
             ^-- upper bound

如果元素不存在于范围内,则下限和上限相同。在对 8 的二分搜索中:

                 v-- lower bound
1 2 3 4 5 5 5 6 7 9
                 ^-- upper bound

您引用的文章的作者用“小于”和“大于”的等效术语来表达所有这些,因此在搜索 5 时,

       v-- lower bound
t t t t f f f f f f      <-- smaller than?
1 2 3 4 5 5 5 6 7 9
f f f f f f f t t t      <-- greater than?
             ^-- upper bound

在所有这些情况下,C++ 迭代器将直接引用边界后面的元素。也就是说:

  • 在搜索3 时,std::lower_bound 返回的迭代器将引用3,而来自std::upper_bound 的迭代器将引用4
  • 在搜索5 时,std::lower_bound 返回的迭代器将引用第一个5,而来自std::upper_bound 的迭代器将引用6
  • 在搜索8 时,两者都会引用9

这是因为 C++ 标准库中的插入约定是传递一个迭代器,该迭代器引用应该在其之前插入新元素的元素。例如,在

std::vector<int> vec { 1, 3, 4, 5, 5, 5, 6, 7, 9 };
vec.insert(vec.begin() + 1, 2);

vec 将包含1, 2, 3, 4, 5, 5, 5, 6, 7, 9std::lower_boundstd::upper_bound 遵循此约定,以便

vec.insert(std::lower_bound(vec.begin(), vec.end(), 5), 5);
vec.insert(std::upper_bound(vec.begin(), vec.end(), 8), 8);

按需要工作,然后将vec 排序。

更一般地说,这是在 C++ 标准库中指定范围的方式的一种表达方式。范围的开始迭代器是指范围的第一个元素(如果有),而结束迭代器是指直接在范围末尾后面的元素(如果有)。另一种看待它的方式是,std::lower_boundstd::upper_bound 返回的迭代器跨越了搜索范围内与搜索元素等效的元素范围。

如果元素不在范围内,则此范围为空,因此lower_boundupper_bound 返回相同的迭代器,否则lower_bound 返回一个迭代器,该迭代器引用搜索范围内的第一个元素,相当于upper_bound 时的搜索值返回一个迭代器,该迭代器引用直接位于最后一个此类元素后面的元素(如果有)。

【讨论】:

  • 啊,我没有考虑多个值与查询相同的情况。但是,在您的第三个示例中,当元素不存在于范围内时,不是上限 9 和下限 7 吗?
  • 在 C++ 标准库术语中,您从 lower_boundupper_bound 获得的迭代器都将引用 9,因为在此元素之前是可以插入 8 的最低和最高位置。但是,真正可以插入元素的位置总是间隙或末端之一。
  • lower_boundupper_bound 符合 stdlib 中的一般迭代器约定 - vector::insert 也是如此,其中传递 vec.begin() + 1 将使其在当前的第二个元素,以及其他类似的上下文。这样您就可以将lower_boundupper_bound 的结果直接传递给这些函数,并让它们做正确的事情。
  • @JoeBob lower_bound 是第一个不小于 8 的元素,upper_bound 是第一个大于的元素8. 在这两种情况下,都是 9。
【解决方案2】:

如果数组永远是

false … true …

那么你找到的索引之前的索引将永远是假的,除非你在index 0找到真。正如我在上面的评论中提到的,另一个边界情况是如果你没有找到true。然后,最高的 false 将是数组的最后一部分。

【讨论】:

  • 你不能用简单的布尔 if 检查来处理这两个问题吗?例如,if(array[0]==true||array[array.size]==false)return false?另外,代码中的更改将如何解决此问题?
  • @JoeBob 这就是重点。如果xtrue 的索引,则x-1 不一定是false 的界限。你需要说if x &gt; 0 &amp;&amp; !array[x-1](第二部分可选)。
【解决方案3】:

如果没有true 或没有false 值,这两种算法的情况显然不同,这实际上从代码sn-p 中非常明显:如果你找到值所在的最低值是 true 并从该位置减去 1 以找到产生 false 的最大值,因为没有这样的对象,所以会产生不正确的结果。由于算法只是针对不同的元素直接定位适当的元素而不是具有特殊情况,因此也避免了必须处理特殊情况,从而减少了代码量。由于特殊情况代码往往只为每个算法调用执行一次,因此它的执行可能比避免特殊情况稍差。这是值得衡量的。

请注意,尽管问题被标记为 C++,但代码示例不是 C++。因此,它不是惯用的 C++。 C++ 中实现lower_bound()upper_bound() 之类的典型方法是使用适当的迭代器。如果没有合适的元素,这些算法不会“抱怨”,因为它们只会在适当的位置生成一个迭代器,即 std::lower_bound() 的开始迭代器和 std::upper_bound() 的结束迭代器。

【讨论】:

  • 啊,正是出于这个原因,我将它标记为 c++。我不太确定lower_bound 是否应该返回比查询大的最小元素,还是比查询小的最大元素。另外,我不太明白您所说的“由于特殊情况代码往往只为每个算法调用执行一次,它的执行可能比避免特殊情况稍差。”它的表现会如何稍微差一些?单个 if 语句将是两者之间的唯一区别,因此区别可以忽略不计。
猜你喜欢
  • 2020-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-12
  • 2013-06-18
  • 1970-01-01
  • 2017-08-05
  • 2015-12-17
相关资源
最近更新 更多