std :: lower_bound和std :: upper_bound的基本原理?

时间:2014-05-08 23:47:13

标签: c++ stl lower-bound upperbound

STL提供二进制搜索功能std :: lower_bound和std :: upper_bound, 但我倾向于不使用它们因为我无法记住它们的作用, 因为他们的合同对我来说似乎完全不可思议。

只是看着名字, 我猜“lower_bound”可能是“last lower bound”的缩写,
即排序列表中的最后一个元素是< =给定的val(如果有的话) 同样地,我猜“upper_bound”可能是“第一上限”的缩写,
即排序列表中的第一个元素是> =给定的val(如果有的话)。

但是文件说他们做了一些与此不同的事情 - 对我来说似乎是倒退和随机混合的东西。 用文章来解释:
     - lower_bound找到第一个> = val
的元素      - upper_bound找到第一个> VAL

所以lower_bound根本找不到下限;它找到第一个上部绑定!? 并且upper_bound找到第一个严格上限

这有什么意义吗? 你怎么记得它?

11 个答案:

答案 0 :(得分:70)

如果您有[firstlast)范围内的多个元素,其值等于您要搜索的值val,则范围为[l,{ {1}})其中

u

恰好是[l = std::lower_bound(first, last, val) u = std::upper_bound(first, last, val) val范围内等于first的元素范围。因此lastl相等范围的“下限”和“上限”。如果你习惯于以半开放的间隔进行思考,这是有道理的。

(注意u将在一次调用中返回一对中的下限和上限。)

答案 1 :(得分:27)

std::lower_bound

返回指向范围[first,last]中第一个元素的迭代器,该元素不小于(即大于或等于)值。

std::upper_bound

返回一个迭代器,该迭代器指向范围[first,last]中大于而不是值的第一个元素。

因此,通过混合下限和上限,您可以准确描述范围的开始位置和结束位置。

  

这是否有意义?

示例:

想象矢量

std::vector<int> data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 };

auto lower = std::lower_bound(data.begin(), data.end(), 4);

1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                  // ^ lower

auto upper = std::upper_bound(data.begin(), data.end(), 4);

1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                           // ^ upper

std::copy(lower, upper, std::ostream_iterator<int>(std::cout, " "));

打印:4 4 4


http://en.cppreference.com/w/cpp/algorithm/lower_bound

http://en.cppreference.com/w/cpp/algorithm/upper_bound

答案 2 :(得分:9)

在这种情况下,我认为一张图片胜过千言万语。我们假设我们使用它们来搜索以下集合中的2。箭头显示了两者将返回的迭代器:

enter image description here

因此,如果集合中已存在多个具有该值的对象,lower_bound将为您提供一个引用其中第一个的迭代器,upper_bound将给出一个迭代器它指的是紧跟在最后一个之后的对象。

这(除其他外)使返回的迭代器可用作hint的{​​{1}}参数。

因此,如果您使用这些作为提示,您插入的项目将成为具有该值的新第一项(如果您使用insert)或具有该值的最后一项(如果您使用{{1} })。如果集合之前没有包含具有该值的项目,您仍将获得一个可用作lower_bound的迭代器,以将其插入集合中的正确位置。

当然,您也可以在没有提示的情况下插入,但是使用提示可以保证插入以恒定的复杂性完成,前提是要插入的新项目可以在迭代器指向的项目之前插入(如它会在这两种情况下)。

答案 3 :(得分:5)

考虑序列

  

1 2 3 4 5 6 6 6 7 8 9

6的下限是前6的位置。

6的上限是7的位置。

这些位置用作指定6值运行的共同(开始,结束)对。


示例:

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

auto main()
    -> int
{
    vector<int> v = {1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9};
    auto const pos1 = lower_bound( v.begin(), v.end(), 6 );
    auto const pos2 = upper_bound( v.begin(), v.end(), 6 );
    for( auto it = pos1; it != pos2; ++it )
    {
        cout << *it;
    }
    cout << endl;
}

答案 4 :(得分:4)

我接受了Brian的回答,但我刚刚意识到另一种有用的思考方式,这为我增加了清晰度,所以我将其添加为参考。

将返回的迭代器视为指向,而不是在元素* iter,而只是之前该元素,即之间该元素与列表中的前一个元素有一个。以这种方式思考,两个函数的契约变得对称:lower_bound找到从&lt; val到&gt; = val的转换的位置,而upper_bound找到从&lt; = val到&gt; val的转换的位置。或者换句话说,lower_bound是比较等于val的项目范围的开头(即std :: equal_range返回的范围),而upper_bound是它们的结尾。

我希望他们能在文档中像这样(或任何其他好的答案)谈论它;这会让它变得不那么神秘!

答案 5 :(得分:1)

这两个函数非常相似,因为它们将在排序序列中找到一个插入点,以保留排序。如果序列中没有与搜索项相等的现有元素,它们将返回相同的迭代器。

如果您尝试在序列中找到某些内容,请使用lower_bound - 如果找到该元素,它将直接指向该元素。

如果您要插入序列,请使用upper_bound - 它会保留重复项的原始排序。

答案 6 :(得分:1)

源代码实际上还有第二个解释,我发现它对理解函数的含义很有帮助:

lower_bound:找到第一个位置,可以在其中插入 [val] ,而无需更改顺序。

upper_bound:查找可以插入[ val]的最后位置 而不更改顺序。

此[first,last)形成一个可以插入val但仍保持容器原始顺序的范围

lower_bound返回“第一个”,即找到“范围的下边界”

upper_bound返回“最后一个”,即找到“范围的上边界”

答案 7 :(得分:0)

关于std::lower_boundstd::upper_bound是什么,已经有了很好的答案。

我想回答你的问题&#39;如何记住它们&#39;

如果我们使用任何容器的STL begin()end()方法进行类比,它很容易理解/记住。 begin()将起始迭代器返回给容器,而end()返回容器外部的迭代器,我们都知道它们在迭代时有多大用处。

现在,在已排序的容器和给定值上,lower_boundupper_bound将返回该值的迭代器范围,以便于迭代(就像开始和结束一样)

实际用途::

除了在排序列表中使用上述用法来访问给定值的范围之外,upper_bound的一个更好的应用是访问地图中具有多对一关系的数据。

例如,请考虑以下关系: 1 - &gt; a,2 - &gt; a,3 - &gt; a,4 - &gt; b,5 - &gt; c,6 - &gt; c,7 - &gt; c,8 - &gt; c,9 - &gt; c,10 - &gt; ç

以上10个映射可以保存在地图中,如下所示:

numeric_limits<T>::lowest() : UND
1 : a
4 : b
5 : c
11 : UND

可以通过表达式(--map.upper_bound(val))->second访问这些值。

对于从最低到0的T值,表达式将返回UND。 对于T值介于1到3之间的值,它将返回&#39; a&#39;等等..

现在,假设我们有100个数据映射到一个值和100个这样的映射。 这种方法减小了地图的大小,从而提高了效率。

答案 8 :(得分:0)

是。这个问题绝对有道理。当有人给这些函数命名时,他们只考虑带有重复元素的排序数组。如果你有一个包含唯一元素的数组,“std :: lower_bound()”更像是搜索“上限”,除非找到实际的元素。

所以这就是我对这些功能的记忆:

  • 如果您正在进行二分查找,请考虑使用std :: lower_bound(),并阅读手册。 std :: binary_search()也基于它。
  • 如果要在排序的唯一值数组中找到值的“位置”,请考虑使用std :: lower_bound()并阅读手册。
  • 如果您在排序数组中搜索任意任务,请阅读std :: lower_bound()和std :: upper_bound()的手册。

自上次使用这些功能一两个月后未能阅读本手册,几乎肯定会导致错误。

答案 9 :(得分:0)

想象一下,如果您想在val中找到等于[first, last)的第一个元素,该怎么办。首先,您要从首个元素严格小于val的元素中排除,然后再从后一个元素中排除-严格大于val的元素。然后剩余范围为[lower_bound, upper_bound]

答案 10 :(得分:-1)

对于数组或向量:

的std :: LOWER_BOUND: 返回指向

范围中第一个元素的迭代器
  • 小于或等于value。(对于数组或向量按降序排列)
  • 大于或等于value。(对于数组或向量按递增顺序)

的std :: UPPER_BOUND: 返回指向

范围中第一个元素的迭代器
  • 小于值。(对于数组或向量按递减顺序)

  • 大于值。(对于数组或向量按递增顺序)