本文介绍std::lower_bound
与std::upper_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
时,等价于传入comp
为less
(即通过小于符号进行判断),需要注意的是:待查找序列[first, last)
必须满足其能够被comp(element, value)
划分,即序列的前面一部分(可以为空)经过comp
返回true
,后面一部分(可以为空)经过comp
返回false
。
不传入comp
的行为:
value
的第一个元素。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
时,等价于传入comp
为less
(即通过小于符号进行判断),需要注意的是:待查找序列[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_bound
与std::upper_bound
中的要求可以看到,只需要待查找序列能够被comp
(或!comp
)划分(即能找到一个位置,该位置左侧元素通过comp
(或!comp
)与待查找值比较全部为真,右侧全部为假),而在实际使用过程中我们不仅仅会完成划分,我们还需要对序列进行排序,所以这里介绍对于有序序列中的一些常见查找操作。
要求列表单调不下降。这一部分查找,由于要满足划分要求则只能使用std::less
和std::less_equal
语义作为comp
。
注意:该部分中的lambda
表达式是为了方便看清element
与value
的位置,实际使用均可以使用std::less_equal
进行替换(由于默认语义comp=std::less
,所以通常这一部分的操作不会使用std::less
)。
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
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
由于std::lower_bound
与std::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
的位置-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
的大小关系即可。
要求列表单调不上升。这一部分查找,由于要满足划分要求则只能使用std::greater
和std::greater_equal
语义作为comp
。
注意:该部分中的lambda
表达式是为了方便看清element
与value
的位置,实际使用均可以使用std::greater_equal
或者std::greater
进行替换。
对于std::lower_bound
的comp
只需要return element >= value;
(取反后就是小于);
对于std::upper_bound
的comp
只需要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
有了之前的例子,容易得出下面的代码:
// 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
的位置-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
的位置-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
的大小关系即可。
std::lower_bound cppreference
std::upper_bound cppreference
std::lless cppreference
std::less_equal cppreference
std::greater cppreference
std::greater_equal cppreference