上界和下界的基本二进制搜索之间的差异

Difference between basic binary search for upper bound and lower bound?

本文关键字:之间 搜索 二进制      更新时间:2023-10-16

在文章中http://community.topcoder.com/tc?module=Static&d1=教程&d2=binarySearch,作者讨论了二进制搜索。他区分了在某件事是真的地方找到最低值,在某件事情是假的地方找到最高值。正在搜索的数组看起来像:

假-假-真-真

我很好奇为什么这两种情况不同。为什么你不能只找到最低的值,这是真的,然后减去一,找到最高的值,那是假的?

第二版:好的,所以我理解下界和上界。现在,我很难理解,当搜索大于或等于查询的最小整数时,为什么我们不能将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

"

二进制搜索的下限和上限是可以在不破坏排序的情况下插入值的最低和最高位置。(在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

你所指的文章的作者用"小于"answers"大于"的等效术语表示所有这些短语,因此在搜索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);

CCD_ 18将包含CCD_。CCD_ 20和CCD_

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

根据需要工作并将CCD_ 22排序。

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

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

如果数组始终是

false … true …

那么,除非在index 0中找到true,否则在您找到的索引之前的索引将始终为false。另一个边界情况,正如我在上面的评论中提到的,是如果你找不到true。然后,最高的false将是数组的最后一部分。

如果没有truefalse值,这两种算法在应该发生什么的条件上明显不同,这一点从代码片段中非常明显:如果在值为true的地方找到最低值,并从这个位置减去1来找到产生false的最高值,则会产生不正确的结果,因为没有这样的对象。由于算法只是针对不同的元素,直接定位合适的元素,而不是具有特殊情况,因此也避免了必须处理特殊情况,从而减少了代码量。由于每个算法调用往往只执行一次特殊情况代码,因此执行情况可能比避免特殊情况稍差。这也许是值得衡量的。

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