使用std::lower_bound和std::upper_bound解决常见的二分查找问题
来源:互联网 发布:装修设计师知乎 编辑:程序博客网 时间:2024/05/17 02:07
常见二分查找的问题有如下几种:
1,有序数组查找特定的某个值。
2,有序数组查找小于某个值的数字中最大的那个。
3,有序数组查找小于或等于某个值的数字中的最大的那个。
4,有序数组查找大于某个值的数字中最小的那个。
5,有序数组查找大于或等于某个值的数字中的最小的那个。
这里的有序数组指的是升序。
第1种情况最简单,这里略去不谈。
第2种和第3种可以归为同一类,视为求下界。
第4种和第5种可以归为同一类,视为求上界。
文章末尾的第一篇参考资料中给出了这几种情况的实现代码。
除了自己实现之外,STL中也为我们提供了很好的实现,即std::lower_bound和std::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 );
cppreference对此的描述是返回区间[first, last)内第一个不小于(即大于或等于)给定值的元素指针。
它的第一种声明的代码实现如下:
template<class ForwardIt, class T>ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value){ ForwardIt it; typename std::iterator_traits<ForwardIt>::difference_type count, step; count = std::distance(first, last); while (count > 0) { it = first; step = count / 2; std::advance(it, step); if (*it < value) { first = ++it; count -= step + 1; } else count = step; } return first;}经过分析源码,可以得知在二分过程中first指针在使条件(*it < value)成立的情况下不断右移,但它最终返回的是第一个使条件不成立的元素。它可以直接解决第5种情况的问题。其实这里这与第2种情况也有些类似,但是第2种情况需要返回的是最后一个使条件成立的元素。如果我们需要使用std::lower_bound解决第2种情况,只需要取std::lower_bound返回值左边相邻的元素即可,如果返回值已经是最左边的元素,说明要找的值不存在。
STL还提供了另外一种实现,它支持使用者自己提供条件函数。它的实现如下:
template<class ForwardIt, class T, class Compare>ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp){ ForwardIt it; typename std::iterator_traits<ForwardIt>::difference_type count, step; count = std::distance(first,last); while (count > 0) { it = first; step = count / 2; std::advance(it, step); if (comp(*it, value)) { first = ++it; count -= step + 1; } else count = step; } return first;}我们可以将此实现描述为返回区间[first, last)内第一个使comp为false的元素指针。区间[first, last)与comp需要满足一定的性质关系,即左部分区间的元素使得comp为true,右部分区间的元素使得comp为false。我们可以使用自己实现的comp并应用上面提到的办法解决第3种情况的问题。
同样地,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 >ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );cppreference对此的描述是返回区间[first, last)内第一个大于给定值的元素指针。
它的第一种声明的代码实现如下:
template<class ForwardIt, class T>ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T& value){ ForwardIt it; typename std::iterator_traits<ForwardIt>::difference_type count, step; count = std::distance(first,last); while (count > 0) { it = first; step = count / 2; std::advance(it, step); if (!(value < *it)) { first = ++it; count -= step + 1; } else count = step; } return first;}std::upper_bound与std::lower_bound的实现代码仅有一处不同即比较条件(!(value < *it)),它等价于(value >= *it)。这段代码使用std::lower_bound的语义描述为返回第一个使(value >= *it)不成立的元素,也就是第一个使得(*it > value)成立的元素,这也就是std::upper_bound的语义。它可以直接用来解决上面第4种情况的问题。
std::upper_bound也支持使用者自己提供条件函数,它的代码如下:
template<class ForwardIt, class T, class Compare>ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T& value, Compare comp){ ForwardIt it; typename std::iterator_traits<ForwardIt>::difference_type count, step; count = std::distance(first,last); while (count > 0) { it = first; step = count / 2; std::advance(it, step); if (!comp(value, *it)) { first = ++it; count -= step + 1; } else count = step; } return first;}我们可以将此实现描述为返回区间[first, last)内第一个使comp为true的元素指针。区间[first, last)与comp需要满足一定的性质关系,即左部分区间的元素使得comp为false,右部分区间的元素使得comp为true。我们可以自己实现一个comp函数,可以用它来解决上面的第5种情况的问题。
注意,std::lower_bound和std::upper_bound中两个comp函数并不一样,它们的参数顺序不同。
观察上面的代码,可以发现std::lower_bound和std::upper_bound的实现中只在移动first指针而不曾移动last指针,这与我们自己平时实现的二分算法(参考本文末尾第一篇文章中的实现代码)有很大不同。我想这跟STL容器的设计有关,毕竟last指针总是一个常量end(),是无法移动的。这种实现使得面对第2和第3种情况下的问题无法直接解决,需要往左移动。
我写了一个简单的代码,用std::lower_bound和std::upper_bound解决上面提到的几种情况。
它使用了gtest和c++11。
#include <gtest/gtest.h>#include <algorithm>#include <vector>typedef std::vector<int>::const_iterator iter;const int NUMBER_SIZE = 100000;const int TEST_COUNT = 100;int GenARandomNumber() { return ::rand() % 1000;}std::vector<int> GenerateRandomSortedVector(int n) { std::vector<int> numbers(n); for (int i = 0; i < n; ++i) { numbers[i] = GenARandomNumber(); } std::sort(numbers.begin(), numbers.end()); return numbers;}void ShowNumbers(const std::vector<int> & numbers) { for (auto num: numbers) { std::cout << num << " "; } std::cout << std::endl;}template<class Compare>iter FindFirstTrue(const std::vector<int> & numbers, int target, Compare comp) { auto it = numbers.begin(); for (; it != numbers.end(); ++it) { if (comp(*it, target)) { break; } } return it;}template<class Compare>iter FindFirstFalse(const std::vector<int> & numbers, int target, Compare comp) { auto it = numbers.begin(); for (; it != numbers.end(); ++it) { if (!comp(*it, target)) { break; } } return it;}iter FindLessMax(const std::vector<int> & numbers, int target) { auto it = numbers.begin(); for (; it != numbers.end(); ++it) { if (it + 1 != numbers.end() && !(*(it + 1) < target)) { break; } } return it;}iter FindLessEqualMax(const std::vector<int> & numbers, int target) { auto it = numbers.begin(); for (; it != numbers.end(); ++it) { if (it + 1 != numbers.end() && !(*(it + 1) <= target)) { break; } } return it;}iter FindGreaterMin(const std::vector<int> & numbers, int target) { auto it = numbers.begin(); for (; it != numbers.end(); ++it) { if (*it > target) { break; } } return it;}iter FindGreaterEqualMin(const std::vector<int> & numbers, int target) { auto it = numbers.begin(); for (; it != numbers.end(); ++it) { if (*it >= target) { break; } } return it;}TEST(BsearchTest, findLessMax) { std::vector<int> numbers = GenerateRandomSortedVector(NUMBER_SIZE); for (size_t i = 0; i < TEST_COUNT; ++i) { int target = GenARandomNumber(); auto it_line = FindLessMax(numbers, target); auto it_bin = std::lower_bound(numbers.begin(), numbers.end(), target); if (it_bin != numbers.begin() && it_bin != numbers.end()) { --it_bin; } ASSERT_TRUE(it_line == it_bin); }}TEST(BsearchTest, findLessEqualMax) { std::vector<int> numbers = GenerateRandomSortedVector(NUMBER_SIZE); for (size_t i = 0; i < TEST_COUNT; ++i) { int target = GenARandomNumber(); auto it_line = FindLessEqualMax(numbers, target); auto it_bin = std::lower_bound(numbers.begin(), numbers.end(), target, [](int val, int tar) { return val <= tar; }); if (it_bin != numbers.begin() && it_bin != numbers.end()) { --it_bin; } ASSERT_TRUE(it_line == it_bin); }}TEST(BsearchTest, findGreaterMin) { std::vector<int> numbers = GenerateRandomSortedVector(NUMBER_SIZE); for (size_t i = 0; i < TEST_COUNT; ++i) { int target = GenARandomNumber(); auto it_line = FindGreaterMin(numbers, target); auto it_bin = std::upper_bound(numbers.begin(), numbers.end(), target); ASSERT_TRUE(it_line == it_bin); }}TEST(BsearchTest, findGreaterEqualMin) { std::vector<int> numbers = GenerateRandomSortedVector(NUMBER_SIZE); for (size_t i = 0; i < TEST_COUNT; ++i) { int target = GenARandomNumber(); auto it_line = FindGreaterEqualMin(numbers, target); auto it_bin = std::upper_bound(numbers.begin(), numbers.end(), target, [](int tar, int val) { return tar <= val; }); ASSERT_TRUE(it_line == it_bin); }}int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}
如果你对我的文章内容有疑问,或者认为文章中有不正确的地方,欢迎留言。
参考文章:
https://www.cnblogs.com/ider/archive/2012/04/01/binary_search.html。
http://en.cppreference.com/w/cpp/algorithm/lower_bound
http://en.cppreference.com/w/cpp/algorithm/upper_bound
- 使用std::lower_bound和std::upper_bound解决常见的二分查找问题
- std::lower_bound()和std::upper_bound()总结
- STL插入排序std::upper_bound和std::lower_bound的使用,以及通用排序类
- std::upper_bound & lower_bound & equal_range
- 二分查找和lower_bound & upper_bound
- std::map lower_bound,upper_bound的用法举例
- 查找和二分查找 lower_bound upper_bound
- 二分查找,lower_bound,upper_bound
- 【lower_bound】【upper_bound】二分查找
- 二分查找:binary_search、lower_bound和upper_bound的实现
- STL 二分查找 upper_bound和lower_bound用法
- std::set::lower_bound与std::lower_bound的效率问题
- std::set::lower_bound 和std::lower_bound的区别
- std::set::lower_bound 和std::lower_bound的区别
- upper_bound()和lower_bound()的使用
- lower_bound upper_bound 【 二分函数的使用】
- std::pair, std::copy, std::lower_bound, std::back_insert使用
- c++ 二分查找的函数 lower_bound & upper_bound & binary_search
- Unity编辑器中Status窗口详解
- Android N Setttings 零-壹 读源码------first part
- 理解Hibernate的三种实例状态
- conda httperror http none none for url none Anaconda更新失败
- C++基础·四
- 使用std::lower_bound和std::upper_bound解决常见的二分查找问题
- Dagger2基础与进阶
- SHTML和HTML的区别
- 在centos7中安装redis
- Java实现归并排序
- 说说I/O与IPC
- 常见数据库连接
- Nginx配置基础-proxy_pass
- java JVM的内存区域(运行时数据区域)