C++ std::lower_bound,std::upper_bound辨析,常用实例

前言

本文介绍std::lower_boundstd::upper_bound同时,给出一些常见的操作实现。

std::lower_bound

template< class ForwardIt, class T >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );
template< class ForwardIt, class T, class Compare >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );

上述为std::lower_bound的两个版本定义,该函数的行为为:返回第一个满足!comp(element, value)的元素迭代器,其中element[first, last)指向的数据,当不传入comp时,等价于传入compless<T>()(即通过小于符号进行判断),需要注意的是:待查找序列[first, last)必须满足其能够被comp(element, value)划分,即序列的前面一部分(可以为空)经过comp返回true,后面一部分(可以为空)经过comp返回false
不传入comp的行为:

  • 查找不降序列中大于等于value的第一个元素。

std::upper_bound

template< class ForwardIt, class T >
ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value );
template< class ForwardIt, class T, class Compare >
constexpr ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );

上述为std::upper_bound的两个版本定义,该函数的行为为:返回第一个满足comp(value, element)的元素迭代器,其中element[first, last)指向的数据,当不传入comp时,等价于传入compless<T>()(即通过小于符号进行判断),需要注意的是:待查找序列[first, last)必须满足其能够被!comp(value, element)划分,即序列的前面一部分(可以为空)经过!comp返回true,后面一部分(可以为空)经过!comp返回false
不传入comp的行为:

  • 查找不降序序列中的大于value的第一个元素。

两者的实现原理

二者的实现原理均为二分查找,不过需要注意的是,传入的序列只有在能够随机访问时,其复杂度才是对数级别的,若传入的序列只支持顺序访问,那么复杂度则为线性。
常见的支持随机访问的容器:

  • 数组
  • std::vector

常见的顺序访问容器:

  • std::set
  • std::map
  • std::multiset
  • std::multimap

注意:对于顺序访问的容器,由于其一般基于搜索二叉树实现,其通常拥有lower_bound以及upper_bound的成员函数(这两个函数的语义与前文介绍的std::lower_bound和std::upper_bound相同),通过该成员函数进行查找复杂度同样为对数级别。

常见实例

按照std::lower_boundstd::upper_bound中的要求可以看到,只需要待查找序列能够被comp(或!comp)划分(即能找到一个位置,该位置左侧元素通过comp(或!comp)与待查找值比较全部为真,右侧全部为假),而在实际使用过程中我们不仅仅会完成划分,我们还需要对序列进行排序,所以这里介绍对于有序序列中的一些常见查找操作。

不降序列中的查找

要求列表单调不下降。这一部分查找,由于要满足划分要求则只能使用std::lessstd::less_equal语义作为comp

注意:该部分中的lambda表达式是为了方便看清elementvalue的位置,实际使用均可以使用std::less_equal<TypeName>()进行替换(由于默认语义comp=std::less<TypeName>(),所以通常这一部分的操作不会使用std::less)。

查找第一个大于value的位置

std::upper_bound的默认实现,既不传入comp参数:

std::upper_bound(a.begin(), a.end(), value);

如果要获取下标:

size_t pos = std::upper_bound(a.begin(), a.end(), value) - a.begin();

(a.begin() + pos)即为指向查找到元素的迭代器(若没找到,则其等于a.end())。
当然也可以用std::lower_bound实现上述的功能:

auto iter = std::lower_bound(a.begin(), a.end(), value, [] (auto element, auto value) {
	return element <= value;
});
size_t pos = iter - a.begin(); // subscript

查找第一个大于等于value的位置

std::lower_bound的默认实现,既不传入comp参数:

auto iter = std::lower_bound(a.begin(), a.end(), value);
size_t pos = iter - a.begin(); // subscript

当然也可以通过std::upper_bound实现:

auto iter = std::upper_bound(a.begin(), a.end(), value, [] (auto value, auto element) {
	return value <= element;
});
size_t pos = iter - a.begin(); //subscript

查找最后一个小于value的位置

由于std::lower_boundstd::upper_bound均只能查找第一个满足条件的元素,因此我们需要进行转换:最后一个小于value的元素位置等价于第一个大于等于value的位置-1
那么代码就很容易可以写出:

// find through std::lower_bound
auto iter = std::lower_bound(a.begin(), a.end(), value) - 1;
size_t pos = iter - a.begin(); // subscript

// find through std::upper_bound
auto iter = std::upper_bound(a.begin(), a.end(), value, [] (auto value, auto element) {
	return value <= element;
}) - 1;
size_t pos = iter - a.begin(); //subscript

查找最后一个小于等于value的位置

同样先进进行转换:最后一个小于等于value的位置等价于第一个大于value的位置-1
故容易得到下面的代码:

// find through std::upper_bound
std::upper_bound(a.begin(), a.end(), value) - 1;
size_t pos = std::upper_bound(a.begin(), a.end(), value) - a.begin();

// find through std::lower_bound
auto iter = std::lower_bound(a.begin(), a.end(), value, [] (auto element, auto value) {
	return element <= value;
}) - 1;
size_t pos = iter - a.begin(); // subscript

查找第一个小于value的位置

由于序列单调不下降,该查找直接通过比较序列的第一个元素和待查找元素即可进行判断:

  • 若第一个元素小于value,那么第一个元素即为第一个小于value的元素;
  • 若第一个元素不小于value,则不存在这样的元素。

查找第一个小于等于value的位置

该部分与查找第一个小于value位置同理,只需要比较第一个元素与待查找值的关系。

查找最后一个大于value的位置

该部分只需要比较最后一个元素与value的大小关系即可。

查找最后一个大于等于value的位置

该部分只需要比较最后一个元素与value的大小关系即可。

不升序列中的查找

要求列表单调不上升。这一部分查找,由于要满足划分要求则只能使用std::greaterstd::greater_equal语义作为comp
注意:该部分中的lambda表达式是为了方便看清elementvalue的位置,实际使用均可以使用std::greater_equal<TypeName>()或者std::greater<TypeName>()进行替换。

查找第一个小于value的位置

对于std::lower_boundcomp只需要return element >= value;(取反后就是小于);
对于std::upper_boundcomp只需要return element < value;(不需要取反)

// find through std::lower_bound
auto iter = std::lower_bound(a.begin(), a.end(), value, [] (auto element, auto value) {
	return element >= value;
});
size_t pos = iter - a.begin(); // subscript

// find through std::upper_bound
auto iter = std::upper_bound(a.begin(), a.end(), value, [] (auto value, auto element) {
	return value > element;
});
size_t pos = iter - a.begin(); // subscript

查找第一个小于等于value的位置

有了之前的例子,容易得出下面的代码:

// find through std::lower_bound
auto iter = std::lower_bound(a.begin(), a.end(), value, [] (auto element, auto value) {
	return element > value;
});
size_t pos = iter - a.begin(); // subscript

// find through std::upper_bound
auto iter = std::upper_bound(a.begin(), a.end(), value, [] (auto value, auto element) {
	return value >= element;
});
size_t pos = iter - a.begin(); // subscript

查找最后一个大于value的位置

同样我们需要转换成查找第一个:最后一个大于value的位置等价于第一个小于等于value的位置-1
那么借助前面的代码,容易得出以下代码:

// find through std::lower_bound
auto iter = std::lower_bound(a.begin(), a.end(), value, [] (auto element, auto value) {
	return element > value;
}) - 1;
size_t pos = iter - a.begin(); // subscript

// find through std::upper_bound
auto iter = std::upper_bound(a.begin(), a.end(), value, [] (auto value, auto element) {
	return value >= element;
}) - 1;
size_t pos = iter - a.begin(); // subscript

查找最后一个大于等于value的位置

转换为查找第一个:最后一个大于等于value的位置等价于第一个小于value的位置-1
那么借助前面的代码,容易得出以下代码:

// find through std::lower_bound
auto iter = std::lower_bound(a.begin(), a.end(), value, [] (auto element, auto value) {
	return element >= value;
});
size_t pos = iter - a.begin(); // subscript

// find through std::upper_bound
auto iter = std::upper_bound(a.begin(), a.end(), value, [] (auto value, auto element) {
	return value > element;
});
size_t pos = iter - a.begin(); // subscript

查找第一个大于value的位置

只需要判断第一个元素与value的大小关系即可。

查找第一个大于等于value的位置

只需要判断第一个元素与value的大小关系即可。

查找最后一个小于value的位置

只需要判断最后一个元素与value的大小关系即可。

查找最后一个小于等于value的位置

只需要判断最后一个元素与value的大小关系即可。

std::lower_bound与std::upper_bound的命名

对于默认的std::lower_boundstd::upper_bound其默认行为实际上分别为:返回升序排序的序列中能够插入的下界和上界。

例如:{1, 2, 3, 3, 3, 4, 5, 6}如果要查找3。插入后不改变升序规则下界下标是2(下标从0开始计数);插入后不改变升序规则上界下标是5

参考

std::lower_bound cppreference
std::upper_bound cppreference
std::lless cppreference
std::less_equal cppreference
std::greater cppreference
std::greater_equal cppreference

  • 16
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值