lower_bound
和 upper_bound
是 C++ 标准模板库(STL)中两个非常常用的算法,主要用于在有序范围内执行二分查找操作。它们定义在 <algorithm>
头文件中。以下是关于这两个函数的详细用法和相关知识。
基本概念
lower_bound
:在有序范围内查找第一个不小于给定值的元素。upper_bound
:在有序范围内查找第一个大于给定值的元素。
这两个函数通常用于已排序的序列(如 vector
、array
、deque
等)中,以实现高效的查找操作,时间复杂度为 O(log n)。
函数原型
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);
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);
- 参数说明:
first
,last
:表示要查找的范围,必须是已排序的序列。value
:要查找的值。comp
:可选的比较函数,用于自定义排序规则。
使用示例
1. 基本用法
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 4, 4, 5, 7, 8};
// 查找第一个不小于4的元素
auto lb = std::lower_bound(v.begin(), v.end(), 4);
if (lb != v.end()) {//这里需要进行判断,假如没有找到会返回v.end(),而v.end() 不是有效的容器元素,它只是表示容器的末尾边界。不能对 v.end() 进行解引用,否则会导致程序崩溃(访问无效内存)
std::cout << "lower_bound: " << *lb << " at position " << (lb - v.begin()) << std::endl;
}
// 查找第一个大于4的元素
auto ub = std::upper_bound(v.begin(), v.end(), 4);
if (ub != v.end()) {
std::cout << "upper_bound: " << *ub << " at position " << (ub - v.begin()) << std::endl;
}
return 0;
}
输出:
lower_bound: 4 at position 2
upper_bound: 5 at position 4
2. 计算某个元素的出现次数
由于 lower_bound
返回第一个不小于目标值的迭代器,upper_bound
返回第一个大于目标值的迭代器,两者的差值即为目标值的出现次数。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 4, 4, 4, 5, 7, 8};
int target = 4;
int count = std::upper_bound(v.begin(), v.end(), target) -
std::lower_bound(v.begin(), v.end(), target);
std::cout << "Number of " << target << " in vector: " << count << std::endl;
return 0;
}
输出:
Number of 4 in vector: 3
3. 使用自定义比较函数
假设有一组自定义结构体,需要按某个字段排序并查找,可以通过提供自定义比较函数来使用 lower_bound
和 upper_bound
。
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
struct Person {
std::string name;
int age;
};
// 比较函数,根据年龄排序
bool compareByAge(const Person& a, const Person& b) {
return a.age < b.age;
}
int main() {
std::vector<Person> people = {
{"Alice", 25},
{"Bob", 30},
{"Charlie", 30},
{"David", 35}
};
// 按年龄排序(假设已排序)
// 查找第一个不小于30岁的人员
auto lb = std::lower_bound(people.begin(), people.end(), Person{"", 30},
[](const Person& a, const Person& b) {
return a.age < b.age;
});
if (lb != people.end()) {
std::cout << "lower_bound: " << lb->name << ", " << lb->age << std::endl;
}
// 查找第一个大于30岁的人员
auto ub = std::upper_bound(people.begin(), people.end(), Person{"", 30},
[](const Person& a, const Person& b) {
return a.age < b.age;
});
if (ub != people.end()) {
std::cout << "upper_bound: " << ub->name << ", " << ub->age << std::endl;
}
return 0;
}
输出:
lower_bound: Bob, 30
upper_bound: David, 35
注意事项
-
范围必须已排序:
lower_bound
和upper_bound
仅在已排序的序列中才保证正确性。如果序列未排序,结果将不可靠。 -
返回值:两个函数都返回一个迭代器,指向满足条件的第一个元素。如果所有元素均不满足条件,返回
last
迭代器。 -
自定义比较函数:当使用自定义排序规则时,确保
lower_bound
和upper_bound
使用相同的比较函数,以保持一致性。 -
适用范围:这些函数不仅适用于
vector
,还适用于任何支持随机访问迭代器的容器,如array
、deque
等。
应用场景
- 查找元素位置:快速查找某个元素在有序序列中的位置。
- 统计元素个数:结合
lower_bound
和upper_bound
计算某个元素的出现次数。 - 区间查找:在有序序列中查找满足特定范围的元素。
- 处理多重集合:在处理包含重复元素的集合时,快速定位元素范围。
总结
lower_bound
和 upper_bound
是在有序序列中执行高效查找的强大工具。理解它们的工作原理和正确使用方式,可以大大提升代码的性能和可读性。在实际编程中,这两个函数常与其他 STL 算法和数据结构结合使用,解决各种复杂的问题。
迭代器(Iterator)在 C++ 中并不是直接等同于指针,也不是简单的从 0
开始的下标。虽然它们有一些相似之处,但迭代器是一个抽象的概念,可以看作是一个统一的接口,用于遍历容器中的元素。迭代器的设计使得它可以对不同类型的容器(如数组、std::vector
、std::list
等)进行访问,而不需要了解容器的内部实现细节。
迭代器与指针的关系
在许多情况下,尤其是对于 顺序容器(如 std::vector
, std::array
, std::deque
等),迭代器的实现确实是基于指针的。它们可以通过类似指针的方式进行递增、解引用和比较等操作。例如:
- 解引用操作:
*iterator
,和解引用指针类似,访问迭代器指向的元素。 - 递增操作:
++iterator
,和指针递增类似,迭代器指向下一个元素。 - 下标操作:某些容器(如
std::vector
)的迭代器可以像指针一样,通过iterator[i]
来访问元素。
但是,迭代器本身是一种更通用的抽象,可以用于多种不同类型的容器。
迭代器与下标的关系
迭代器也可以被看作是容器元素的指针,允许通过递增操作逐步访问容器中的元素。但与下标直接访问元素不同,迭代器可以与不同的容器类型配合使用,而不仅限于顺序容器。
对于像 std::vector
这样的顺序容器,可以通过将迭代器减去容器的 begin()
迭代器来计算它距离容器开头的距离,实际上类似于使用下标来访问容器元素的方式。例如:
std::vector<int> v = {10, 20, 30, 40};
auto it = v.begin() + 2; // 迭代器指向第三个元素
std::cout << *it << std::endl; // 输出 30
std::cout << (it - v.begin()) << std::endl; // 输出 2,类似下标
然而,这种操作通常不是所有类型的容器都支持的。例如,std::list
是一个双向链表,它的迭代器不能像指针那样进行直接的加法和减法操作。
迭代器的类型
C++ 中有多种类型的迭代器,主要包括:
- 输入迭代器:只能单向遍历容器(只能递增)。
- 输出迭代器:用于输出操作。
- 前向迭代器:可以单向遍历容器,可以多次访问当前元素。
- 双向迭代器:除了单向遍历外,还支持反向遍历(递增和递减)。
- 随机访问迭代器:支持类似指针的所有操作,包括加法、减法、比较和下标操作。
对于 std::vector
和 std::deque
等顺序容器,迭代器通常是 随机访问迭代器,允许你像指针一样进行算术运算。而对于 std::list
这样的链表容器,迭代器是 双向迭代器,不能进行随机访问,但可以支持双向遍历。
迭代器和指针的对比
特性 | 迭代器 | 指针 |
---|---|---|
访问容器元素 | 可以通过 *it 解引用 | 通过 *ptr 解引用 |
递增(移动) | ++it | ++ptr |
随机访问 | 对于随机访问容器支持,it + n | 支持 |
容器适用性 | 适用于各种容器 | 只适用于数组和某些容器 |
类型安全性 | 通过模板提供类型安全 | 需要手动管理类型 |
小结
- 迭代器 是一种通用的抽象,可以看作是容器中元素的“指针”,但是它不仅限于指针,也可以用于其他容器类型。
- 对于 顺序容器,迭代器通常是基于指针的,支持类似指针的操作(如递增、解引用)。
- 迭代器可以提供比指针更为灵活和通用的接口,适用于不同类型的容器。
- 对于
lower_bound
和upper_bound
等算法,返回的是容器中的迭代器,而不是简单的下标或者指针。需要检查返回的迭代器是否等于v.end()
,以确保它指向一个有效的元素。