lower_bound 和 upper_bound

lower_boundupper_bound 是 C++ 标准模板库(STL)中两个非常常用的算法,主要用于在有序范围内执行二分查找操作。它们定义在 <algorithm> 头文件中。以下是关于这两个函数的详细用法和相关知识。

基本概念

  • lower_bound:在有序范围内查找第一个不小于给定值的元素。
  • upper_bound:在有序范围内查找第一个大于给定值的元素。

这两个函数通常用于已排序的序列(如 vectorarraydeque 等)中,以实现高效的查找操作,时间复杂度为 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_boundupper_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

注意事项

  1. 范围必须已排序lower_boundupper_bound 仅在已排序的序列中才保证正确性。如果序列未排序,结果将不可靠。

  2. 返回值:两个函数都返回一个迭代器,指向满足条件的第一个元素。如果所有元素均不满足条件,返回 last 迭代器。

  3. 自定义比较函数:当使用自定义排序规则时,确保 lower_boundupper_bound 使用相同的比较函数,以保持一致性。

  4. 适用范围:这些函数不仅适用于 vector,还适用于任何支持随机访问迭代器的容器,如 arraydeque 等。

应用场景

  • 查找元素位置:快速查找某个元素在有序序列中的位置。
  • 统计元素个数:结合 lower_boundupper_bound 计算某个元素的出现次数。
  • 区间查找:在有序序列中查找满足特定范围的元素。
  • 处理多重集合:在处理包含重复元素的集合时,快速定位元素范围。

总结

lower_boundupper_bound 是在有序序列中执行高效查找的强大工具。理解它们的工作原理和正确使用方式,可以大大提升代码的性能和可读性。在实际编程中,这两个函数常与其他 STL 算法和数据结构结合使用,解决各种复杂的问题。

迭代器(Iterator)在 C++ 中并不是直接等同于指针,也不是简单的从 0 开始的下标。虽然它们有一些相似之处,但迭代器是一个抽象的概念,可以看作是一个统一的接口,用于遍历容器中的元素。迭代器的设计使得它可以对不同类型的容器(如数组、std::vectorstd::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::vectorstd::deque 等顺序容器,迭代器通常是 随机访问迭代器,允许你像指针一样进行算术运算。而对于 std::list 这样的链表容器,迭代器是 双向迭代器,不能进行随机访问,但可以支持双向遍历。

迭代器和指针的对比

特性迭代器指针
访问容器元素可以通过 *it 解引用通过 *ptr 解引用
递增(移动)++it++ptr
随机访问对于随机访问容器支持,it + n支持
容器适用性适用于各种容器只适用于数组和某些容器
类型安全性通过模板提供类型安全需要手动管理类型

小结

  • 迭代器 是一种通用的抽象,可以看作是容器中元素的“指针”,但是它不仅限于指针,也可以用于其他容器类型。
  • 对于 顺序容器,迭代器通常是基于指针的,支持类似指针的操作(如递增、解引用)。
  • 迭代器可以提供比指针更为灵活和通用的接口,适用于不同类型的容器。
  • 对于 lower_boundupper_bound 等算法,返回的是容器中的迭代器,而不是简单的下标或者指针。需要检查返回的迭代器是否等于 v.end(),以确保它指向一个有效的元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值