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找到第一个严格上限。
这有什么意义吗? 你怎么记得它?
答案 0 :(得分:70)
如果您有[first
,last
)范围内的多个元素,其值等于您要搜索的值val
,则范围为[l
,{ {1}})其中
u
恰好是[l = std::lower_bound(first, last, val)
u = std::upper_bound(first, last, val)
,val
范围内等于first
的元素范围。因此last
和l
是相等范围的“下限”和“上限”。如果你习惯于以半开放的间隔进行思考,这是有道理的。
(注意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
答案 2 :(得分:9)
在这种情况下,我认为一张图片胜过千言万语。我们假设我们使用它们来搜索以下集合中的2
。箭头显示了两者将返回的迭代器:
因此,如果集合中已存在多个具有该值的对象,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_bound
和std::upper_bound
是什么,已经有了很好的答案。
我想回答你的问题&#39;如何记住它们&#39; ?
如果我们使用任何容器的STL begin()
和end()
方法进行类比,它很容易理解/记住。 begin()
将起始迭代器返回给容器,而end()
返回容器外部的迭代器,我们都知道它们在迭代时有多大用处。
现在,在已排序的容器和给定值上,lower_bound
和upper_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()”更像是搜索“上限”,除非找到实际的元素。
所以这就是我对这些功能的记忆:
自上次使用这些功能一两个月后未能阅读本手册,几乎肯定会导致错误。
答案 9 :(得分:0)
想象一下,如果您想在val
中找到等于[first, last)
的第一个元素,该怎么办。首先,您要从首个元素严格小于val
的元素中排除,然后再从后一个元素中排除-严格大于val
的元素。然后剩余范围为[lower_bound, upper_bound]
答案 10 :(得分:-1)
对于数组或向量:
的std :: LOWER_BOUND: 返回指向
范围中第一个元素的迭代器的std :: UPPER_BOUND: 返回指向
范围中第一个元素的迭代器小于值。(对于数组或向量按递减顺序)
大于值。(对于数组或向量按递增顺序)