【从C到C++的算法竞赛迁移指南】第四篇:STL算法与迭代器 —— 解锁数据处理新维度

系列导航:

  1. [第一篇] 基础语法与竞赛优势
  2. [第二篇] 动态数组与字符串革命
  3. [第三篇] 映射与集合的终极形态
  4. [▶ 本篇] STL算法与迭代器
  5. [第五篇] 现代语法糖精粹
  6. [第六篇] 竞赛实战技巧


一、迭代器:从C指针到C++智能导航

1.1 指针与迭代器的本质联系

C指针的局限性
int arr[5] = {1,2,3,4,5};
int* p = arr;
printf("%d\n", *(p+3)); // 输出4

// 典型问题场景:
// 1. 越界访问无保护
// 2. 动态扩容导致指针失效
// 3. 复杂数据结构操作困难
C++迭代器的进化
vector<int> vec = {1,2,3,4,5};
auto it = vec.begin() + 3;
cout << *it << endl; // 输出4

// 迭代器优势:
// 1. 类型安全(自动关联容器类型)
// 2. 边界检查(debug模式下)
// 3. 统一访问接口
// 4. 内存管理自动化

1.2 迭代器的解剖学(以vector为例)

迭代器核心操作全解
vector<int> nums{1,2,3,4,5};

// 基础操作
auto begin = nums.begin(); // 首元素迭代器
auto end = nums.end();     // 尾后迭代器
cout << *begin;            // 解引用 -> 1
cout << *(end-1);          // 尾元素 -> 5

// 算术运算(仅随机访问迭代器)
auto mid = begin + nums.size()/2; // 中间位置
cout << *mid;                     // 3

// 比较运算
if (begin < end) { /* 有效区间 */ }

// 遍历模式
for (auto it = begin; it != end; ++it) {
    cout << *it << " ";
}

// 实战技巧:快速交换
iter_swap(begin, begin+1); // 交换前两个元素
迭代器失效的七大场景(必知!)
容器类型修改操作失效规则
vectorpush_back所有迭代器可能失效
insert/erase被修改位置之后的迭代器失效
deque头尾插入所有迭代器失效
中间插入所有迭代器失效
list任何位置插入/删除只有被删除元素迭代器失效
map/set插入/删除只有被删除元素迭代器失效

示例代码:

vector<int> vec = {1,2,3};
auto it = vec.begin();
vec.push_back(4); // 可能导致迭代器失效!
cout << *it;      // 未定义行为!

1.3 迭代器分类的实战意义

各类型迭代器支持操作详解
操作符输入输出前向双向随机访问
++it / it++
–it / it–
*it (读)
*it (写)
it->member
it1 == it2 / it1 != it2
it + n / it - n
it[n]
it1 < it2
容器与迭代器类型对应表
容器迭代器类型典型限制
array随机访问固定大小
vector随机访问动态扩容
deque随机访问分段存储
list双向不支持随机访问
forward_list前向只能单向遍历
set/map双向按序遍历
unordered_set/map前向哈希表遍历

关键差异示例:

// 正确用法(vector支持随机访问)
vector<int> v{1,2,3};
sort(v.begin(), v.end());

// 错误用法(list需要双向迭代器)
list<int> lst{3,1,2};
sort(lst.begin(), lst.end()); // 编译错误!
lst.sort(); // 正确用法:调用成员函数

1.4 迭代器的底层实现揭秘

vector迭代器伪代码实现
template <typename T>
class vector_iterator {
    T* ptr;
public:
    explicit vector_iterator(T* p) : ptr(p) {}
    
    // 解引用
    T& operator*() { return *ptr; }
    
    // 前++
    vector_iterator& operator++() {
        ++ptr;
        return *this;
    }
    
    // 后++
    vector_iterator operator++(int) {
        vector_iterator tmp = *this;
        ++ptr;
        return tmp;
    }
    
    // 随机访问
    vector_iterator operator+(int n) {
        return vector_iterator(ptr + n);
    }
    
    // 比较运算符
    bool operator!=(const vector_iterator& other) {
        return ptr != other.ptr;
    }
};
list迭代器实现要点
template <typename T>
class list_iterator {
    list_node<T>* current;
public:
    // 双向移动
    list_iterator& operator++() {
        current = current->next;
        return *this;
    }
    
    list_iterator& operator--() {
        current = current->prev;
        return *this;
    }
    
    // 无法实现operator+(非随机访问)
};

1.5 迭代器高级技巧

插入迭代器三剑客
vector<int> src{1,2,3};

// 1. 尾部插入
vector<int> dest1;
copy(src.begin(), src.end(), back_inserter(dest1));

// 2. 头部插入
deque<int> dest2;
copy(src.begin(), src.end(), front_inserter(dest2));

// 3. 指定位置插入
list<int> dest3{9,9};
auto it = dest3.begin();
advance(it, 1);
copy(src.begin(), src.end(), inserter(dest3, it));
// 结果:9,1,2,3,9
流迭代器实战
// 从文件读取数字
ifstream fin("data.txt");
vector<double> data;
copy(istream_iterator<double>(fin),
     istream_iterator<double>(),
     back_inserter(data));

// 格式化输出
cout << "Sorted: ";
copy(data.begin(), data.end(),
     ostream_iterator<double>(cout, " $\n"));
逆向迭代器妙用
string s = "hello";
cout << string(s.rbegin(), s.rend()) << endl; // 输出olleh

// 查找最后一个空格
string text = "C++ is awesome";
auto last_space = find(text.rbegin(), text.rend(), ' ');
if (last_space != text.rend()) {
    cout << "最后一个空格位置:" 
         << (text.rend() - last_space - 1);
}

1.6 迭代器的性能陷阱与优化

遍历方式对比测试(1e6元素vector)
遍历方式时间(ms)内存占用
下标访问123.8MB
迭代器访问133.8MB
range-based for133.8MB
逆向迭代器153.8MB

结论:现代编译器对迭代器的优化已达到原生指针水平

缓存友好性优化
// 差:链表遍历(缓存不友好)
list<int> big_list(1e6);
for (auto& x : big_list) { ... }

// 优:vector局部性访问
vector<int> vec(1e6);
for (auto it=vec.begin(); it!=vec.end(); ++it) {
    // 顺序访问利用缓存预取
}

1.7 常见问题深度解析

Q1:迭代器与索引如何选择?

vector<int> vec{1,2,3,4,5};

// 索引方案
for (size_t i=0; i<vec.size(); ++i) {
    if (vec[i] == 3) {
        vec.erase(vec.begin() + i);
        break;
    }
}

// 迭代器方案
for (auto it=vec.begin(); it!=vec.end(); ) {
    if (*it == 3) {
        it = vec.erase(it);
        break;
    } else {
        ++it;
    }
}

结论:修改操作时优先使用迭代器,遍历时两者性能相当

Q2:如何安全地遍历并修改容器?

vector<int> data{1,2,3,4,5};

// 错误方式:直接修改迭代器指向的值
for (auto it=data.begin(); it!=data.end(); ++it) {
    *it *= 2; // 安全
    if (*it > 5) {
        data.erase(it); // 危险!会导致迭代器失效
    }
}

// 正确方式:捕获erase返回值
for (auto it=data.begin(); it!=data.end(); ) {
    if (*it > 5) {
        it = data.erase(it);
    } else {
        ++it;
    }
}

Q3:自定义迭代器实现案例

class PrimeIterator {
    int current;
public:
    PrimeIterator(int start=2) : current(start) {}
    
    bool operator!=(const PrimeIterator& other) {
        return current != other.current;
    }
    
    int operator*() const { return current; }
    
    PrimeIterator& operator++() {
        do {
            ++current;
        } while (!is_prime(current));
        return *this;
    }
    
private:
    bool is_prime(int n) {
        if (n <= 1) return false;
        for (int i=2; i*i<=n; ++i)
            if (n%i == 0) return false;
        return true;
    }
};

// 使用示例
for (PrimeIterator it; *it < 100; ++it) {
    cout << *it << " ";
}
// 输出:2 3 5 7 11 ... 97

迭代器思维导图

迭代器类型
输入迭代器
输出迭代器
前向迭代器
双向迭代器
随机访问
只读单次遍历
只写单次遍历
多次读写前进
可双向移动
支持随机访问
vector,deque,array
list,set,map
forward_list
istream
ostream

二、sort函数:重新定义排序

2.1 从qsort到sort:跨越时代的进化

C语言qsort的痛点分析
/* 经典qsort使用示例 */
int cmp(const void* a, const void* b) {
    return *(int*)a - *(int*)b; // 潜在溢出风险!
}
int arr[5] = {5,3,1,4,2};
qsort(arr, 5, sizeof(int), cmp);
  • 类型不安全:需要手动处理void*转换
  • 性能损失:每次比较都需要函数调用
  • 可读性差:回调函数与主逻辑分离
  • 维护困难:复杂数据结构需要多层解引用
C++ sort的革命性突破
vector<int> vec = {5,3,1,4,2};

// 默认升序排序
sort(vec.begin(), vec.end());

// 降序排列(lambda表达式版本)
sort(vec.begin(), vec.end(), [](int a, int b) {
    return a > b; 
});

// 使用标准库比较函数对象
sort(vec.begin(), vec.end(), greater<int>());
  • 类型安全:模板自动推导元素类型
  • 性能优化:比较操作可内联(比qsort快约40%)
  • 灵活接口:支持函数指针、函数对象、lambda
  • 范围明确:使用迭代器指定排序区间

2.2 性能对比:底层原理深度解析

测试数据(1e6个int排序)
排序方式时间(ms)内存占用(MB)
qsort1203.8
sort823.8
性能优势的关键因素:
  1. 内联优化:sort的比较操作在编译期确定
  2. 模板特化:对基础类型采用优化算法
  3. 减少间接调用:避免qsort的函数指针跳转
  4. 混合排序策略:结合快速排序+插入排序+堆排序

2.3 结构体排序:从C到C++的范式转换

C语言方式(qsort)
typedef struct {
    char name[20];
    int score;
} Student;

int cmp(const void* a, const void* b) {
    const Student* sa = (const Student*)a;
    const Student* sb = (const Student*)b;
    return sb->score - sa->score; // 降序排列
}

Student students[100];
qsort(students, 100, sizeof(Student), cmp);
C++现代写法
struct Student {
    string name;   // 使用更安全的string类型
    int score;
    double gpa;

    // 默认比较规则(可选)
    bool operator<(const Student& other) const {
        return score < other.score;
    }
};

vector<Student> students;

// 方法1:使用lambda表达式
sort(students.begin(), students.end(), [](const auto& a, const auto& b) {
    return a.score > b.score; // 降序排列
});

// 方法2:重载operator<
sort(students.begin(), students.end()); 

// 多条件排序
sort(students.begin(), students.end(), [](const auto& a, const auto& b) {
    return tie(a.score, a.name) > tie(b.score, b.name); 
    // 先按分数降序,分数相同按名字字典序降序
});
关键技巧:
  • 使用const引用:避免拷贝开销
  • tie函数妙用:实现多字段比较(需包含)
  • 运算符重载:定义默认排序规则
  • 结构化绑定(C++17):
    for (const auto& [name, score, gpa] : students) {
        // 直接访问成员
    }
    

2.4 高级应用:自定义排序实战

场景1:字符串特殊排序
vector<string> files = {"img12.jpg", "img10.jpg", "img2.jpg"};

sort(files.begin(), files.end(), [](const string& a, const string& b) {
    // 自然序比较:提取数字部分比较
    regex re("img(\\d+)\\.jpg");
    smatch ma, mb;
    regex_match(a, ma, re);
    regex_match(b, mb, re);
    return stoi(ma[1]) < stoi(mb[1]);
});
// 结果:img2.jpg, img10.jpg, img12.jpg
场景2:二维点集极角排序
struct Point { int x, y; };
vector<Point> points;

sort(points.begin(), points.end(), [](const Point& a, const Point& b) {
    return atan2(a.y, a.x) < atan2(b.y, b.x);
});
场景3:自定义容器排序
deque<float> data = {1.2f, 3.4f, 0.5f};

// 对前2/3部分排序
auto mid = data.begin() + data.size()*2/3;
sort(data.begin(), mid); 

// 输出结果保留两位小数
cout << fixed << setprecision(2);
copy(data.begin(), data.end(), ostream_iterator<float>(cout, " "));

2.5 排序稳定性与算法选择

算法稳定性时间复杂度适用场景
sort不稳定O(n log n)通用排序
stable_sort稳定O(n log^2 n)需要保持相等元素顺序
partial_sort不稳定O(n log k)取前k个有序元素
nth_element不稳定O(n)快速找到第k大元素
// 稳定排序示例
struct Record {
    int id;
    string value;
};

vector<Record> logs;

stable_sort(logs.begin(), logs.end(), [](const auto& a, const auto& b) {
    return a.value < b.value; // 相同value保持原始顺序
});

2.6 常见陷阱与调试技巧

陷阱1:无效的比较函数
// 错误写法:不满足严格弱序
sort(vec.begin(), vec.end(), [](int a, int b) {
    return a <= b; // 应该使用 <
});
陷阱2:迭代器失效
vector<int> vec = {3,1,4};
auto begin = vec.begin(), end = vec.end();
vec.push_back(2); // 可能导致迭代器失效!
sort(begin, end); // 未定义行为
调试技巧:
  • 使用带检查的迭代器(gcc:-D_GLIBCXX_DEBUG)
  • 在比较函数中添加日志:
    sort(vec.begin(), vec.end(), [](int a, int b) {
        cout << "Comparing " << a << " vs " << b << endl;
        return a < b;
    });
    

2.7 竞赛中的sort优化技巧

技巧1:减少拷贝开销
struct HeavyData {
    int key;
    array<char, 1024> payload; // 大内存对象
};

vector<HeavyData> data;

// 按指针排序避免拷贝
vector<HeavyData*> ptrs;
transform(data.begin(), data.end(), back_inserter(ptrs), 
         [](auto& obj) { return &obj; });
sort(ptrs.begin(), ptrs.end(), 
    [](auto a, auto b) { return a->key < b->key; });
技巧2:预先计算比较键
vector<string> words = {"apple", "banana", "cherry"};

// 按字符串长度排序(避免重复计算)
vector<pair<size_t, string>> temp;
transform(words.begin(), words.end(), back_inserter(temp),
         [](const string& s) { return make_pair(s.size(), s); });
sort(temp.begin(), temp.end());
技巧3:手写优化排序
// 针对特定数据的手写排序
void competition_sort(vector<int>& arr) {
    const int N = 1e6;
    static int cnt[N*2+1] = {0}; // 假设数据范围已知
    
    for(int x : arr) cnt[x + N]++;
    arr.clear();
    for(int i=0; i<=2*N; i++)
        while(cnt[i]--) arr.push_back(i - N);
}

2.7 从理论到实践:sort函数综合训练

经典题目重构

题目:统计前K个高频元素(LeetCode 347)

// C++ STL版本
vector<int> topKFrequent(vector<int>& nums, int k) {
    unordered_map<int, int> cnt;
    for(int num : nums) cnt[num]++;
    
    vector<pair<int, int>> freq(cnt.begin(), cnt.end());
    nth_element(freq.begin(), freq.begin()+k, freq.end(),
               [](auto& a, auto& b) { return a.second > b.second; });
    
    vector<int> res;
    transform(freq.begin(), freq.begin()+k, back_inserter(res),
             [](auto& p) { return p.first; });
    return res;
}
性能优化实战
// 优化前:双重排序
sort(arr.begin(), arr.end());
sort(unique(arr.begin(), arr.end()), arr.end());

// 优化后:单次排序+去重
sort(arr.begin(), arr.end());
auto last = unique(arr.begin(), arr.end());
arr.erase(last, arr.end());

2.8 sort函数深度问答

Q1:为什么sort不能直接对list排序?
A1:list只提供双向迭代器,而sort需要随机访问迭代器。对list应使用其成员函数sort()

Q2:如何实现大小写不敏感的字符串排序?

vector<string> words = {"Apple", "banana", "Cherry"};
sort(words.begin(), words.end(), [](const string& a, const string& b) {
    return lexicographical_compare(
        a.begin(), a.end(), b.begin(), b.end(),
        [](char c1, char c2) { return tolower(c1) < tolower(c2); }
    );
});

Q3:如何实现自定义交换操作?

// 定义特化版本的swap
namespace std {
template<>
void swap(MyType& a, MyType& b) noexcept {
    a.custom_swap(b);
}
}

Q4:如何验证排序结果?

// 使用is_sorted算法验证
if(!is_sorted(vec.begin(), vec.end())) {
    cerr << "排序失败!";
}


三、二分查找:从手写到STL的智慧升华

3.1 二分查找的本质理解

算法核心思想图解
初始状态:
[1, 3, 5, 7, 9]  target=6
 ↑        ↑      ↑
left    mid   right

第一次迭代:
mid = 0 + (4-0)/2 = 2 → arr[2]=5 < 6
left = mid + 1 = 3

新状态:
[1, 3, 5, 7, 9]
         ↑ ↑
       mid right

第二次迭代:
mid = 3 + (4-3)/2 = 3 → arr[3]=7 > 6
right = mid - 1 = 2

循环结束:left=3 > right=2
返回left即第一个>=6的位置
关键特性要求
  1. 有序性:必须单调递增/递减
  2. 确定性:比较结果能明确划分区间
  3. 边界处理:开闭区间的一致性
  4. 终止条件:left <= right 或 left < right

3.2 STL二分三剑客深度解析

函数对比表(重点记忆)
函数名返回值条件等效表达式典型应用场景
lower_bound第一个 >= targetarr[i] >= target求元素存在性/插入位置
upper_bound第一个 > targetarr[i] > target统计元素出现次数
binary_search是否存在target简单存在性判断
底层实现源码剖析(简化版)
template <class ForwardIt, class T>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value) {
    ForwardIt it;
    typename iterator_traits<ForwardIt>::difference_type count, step;
    count = distance(first, last);

    while (count > 0) {
        it = first; 
        step = count / 2;
        advance(it, step);
        if (*it < value) {
            first = ++it;
            count -= step + 1;
        } else {
            count = step;
        }
    }
    return first;
}

3.3 六大经典二分问题模板

模板1:精确查找
vector<int> nums{1,3,5,7,9};
auto it = lower_bound(nums.begin(), nums.end(), 5);
if (it != nums.end() && *it == 5) {
    cout << "找到位置:" << it - nums.begin();
}
模板2:范围统计
// 统计[5,8)范围内的元素个数
auto left = lower_bound(nums.begin(), nums.end(), 5);
auto right = upper_bound(nums.begin(), nums.end(), 8);
cout << "数量:" << right - left;
模板3:最大值最小化
// 寻找满足条件的第一个值(eg:分书问题)
int l = 0, r = INT_MAX;
while (l < r) {
    int mid = l + (r-l)/2;
    if (check(mid)) r = mid;
    else l = mid + 1;
}
return l;
模板4:实数二分
double eps = 1e-7;
double l = 0, r = 1e9;
while (r - l > eps) {
    double mid = (l + r)/2;
    if (check(mid)) r = mid;
    else l = mid;
}
模板5:旋转数组查找
vector<int> nums{4,5,6,7,0,1,2};
int target = 0;
int l = 0, r = nums.size()-1;
while (l <= r) {
    int mid = (l+r)/2;
    if (nums[mid] == target) return mid;
    if (nums[l] <= nums[mid]) { // 左半有序
        if (nums[l] <= target && target < nums[mid]) r = mid-1;
        else l = mid+1;
    } else { // 右半有序
        if (nums[mid] < target && target <= nums[r]) l = mid+1;
        else r = mid-1;
    }
}
模板6:二维矩阵搜索
bool searchMatrix(vector<vector<int>>& matrix, int target) {
    if (matrix.empty()) return false;
    int m = matrix.size(), n = matrix[0].size();
    int l = 0, r = m*n - 1;
    while (l <= r) {
        int mid = l + (r-l)/2;
        int val = matrix[mid/n][mid%n];
        if (val == target) return true;
        else if (val < target) l = mid + 1;
        else r = mid - 1;
    }
    return false;
}

3.4 STL二分与手写二分性能对比

测试数据(1e8次查询)
实现方式时间(ms)内存(MB)代码复杂度
手写循环3201.2
STL lower_bound2801.5
递归实现4502.1

性能优势分析:

  1. 内联优化:STL模板在编译期展开
  2. 分支预测:STL使用前进次数计算减少分支
  3. 指令级优化:编译器对STL代码优化更充分
  4. 缓存友好:迭代器访问模式更规律

3.5 自定义比较的四种姿势

方式1:重载operator<
struct Point {
    int x, y;
    bool operator<(const Point& other) const {
        return x < other.x || (x == other.x && y < other.y);
    }
};

vector<Point> points{{1,2}, {3,4}};
sort(points.begin(), points.end());
auto it = lower_bound(points.begin(), points.end(), Point{2,0});
方式2:函数对象
struct CompareY {
    bool operator()(const Point& a, const Point& b) {
        return a.y < b.y;
    }
};

auto it = lower_bound(points.begin(), points.end(), Point{0,3}, CompareY());
方式3:Lambda表达式
auto it = lower_bound(points.begin(), points.end(), 5, 
    [](const Point& p, int val) { return p.x < val; });
方式4:标准库函数对象
vector<string> words{"Apple", "Banana"};
auto it = lower_bound(words.begin(), words.end(), "apple", 
    [](const string& a, const string& b) {
        return lexicographical_compare(
            a.begin(), a.end(), b.begin(), b.end(),
            [](char c1, char c2) { return tolower(c1) < tolower(c2); }
        );
    });

3.6 二分查找的十大常见错误

错误1:死循环
int l=0, r=n-1;
while (l < r) {
    int mid = (l+r)/2; // 当l=1, r=2时 mid=1
    if (check(mid)) r = mid;
    else l = mid;
}
// 正确应改为:mid = l + (r-l+1)/2
错误2:整数溢出
int l = 0, r = INT_MAX;
int mid = (l + r) / 2; // 当l+r > INT_MAX时溢出
// 正确写法:mid = l + (r - l) / 2
错误3:错误比较方向
// 寻找最后一个<=target的元素
auto pos = upper_bound(arr, target) - 1; // 正确
auto pos = lower_bound(arr, target) - 1; // 错误
错误4:未排序使用
vector<int> nums{3,1,2};
auto it = lower_bound(nums.begin(), nums.end(), 2); // 未定义行为!
错误5:迭代器失效
vector<int> data{1,3,5};
auto begin = data.begin(), end = data.end();
data.push_back(7);
auto it = lower_bound(begin, end, 3); // 可能失效!
错误6:浮点数精度
double eps = 1e-6;
while (r - l > eps) { ... } // 正确
while (l < r) { ... }       // 可能无限循环
错误7:错误返回判断
auto it = lower_bound(...);
if (it != end) { ... } // 缺少*it == target检查
错误8:多维数据比较
// 错误:仅比较x坐标
auto it = lower_bound(points.begin(), points.end(), target, 
    [](auto& a, auto& b) { return a.x < b.x; });
错误9:修改搜索区间
while (...) {
    mid = (l+r)/2;
    if (check(mid)) {
        ans = mid;
        l = mid + 1; // 应保持区间不变
    }
}
错误10:错误初始化边界
// 寻找最小满足值,初始r应为可能最大值
int l=0, r=arr.size()-1; // 错误,应设为理论最大值

3.7 竞赛中的二分优化技巧

技巧1:预处理+二分
// 查询区间和问题
vector<int> prefix(nums.size()+1, 0);
partial_sum(nums.begin(), nums.end(), prefix.begin()+1);

auto query = [&](int l, int r) {
    return prefix[r+1] - prefix[l];
};

// 二分查找满足条件的区间
int l=0, r=n-1;
while (l <= r) {
    int mid = (l+r)/2;
    if (query(0, mid) >= target) r = mid-1;
    else l = mid+1;
}
技巧2:答案二分法
// 求平方根(保留整数部分)
int mySqrt(int x) {
    if (x == 0) return 0;
    int l=1, r=x;
    while (l <= r) {
        int mid = l + (r-l)/2;
        if (mid > x/mid) { // 防止溢出
            r = mid-1;
        } else {
            if ((mid+1) > x/(mid+1)) return mid;
            l = mid+1;
        }
    }
    return l;
}
技巧3:STL组合使用
// 求最长递增子序列(LIS)
vector<int> dp;
for (int num : nums) {
    auto it = lower_bound(dp.begin(), dp.end(), num);
    if (it == dp.end()) dp.push_back(num);
    else *it = num;
}
return dp.size();
技巧4:位运算加速
// 快速计算mid(避免除法)
int mid = (l & r) + ((l ^ r) >> 1); // 等价于(l + r)/2

3.8 二分查找的扩展应用

应用1:数学函数求根
double f(double x) { return x*x - 2; } // 求√2
double l=0, r=2, eps=1e-9;
while (r-l > eps) {
    double mid = (l+r)/2;
    if (f(mid) > 0) r = mid;
    else l = mid;
}
应用2:离散化处理
vector<int> nums{500, 200, 300};
vector<int> sorted(nums);
sort(sorted.begin(), sorted.end());
sorted.erase(unique(sorted.begin(), sorted.end()), sorted.end());

auto get_id = [&](int x) {
    return lower_bound(sorted.begin(), sorted.end(), x) - sorted.begin();
};
应用3:时间安排问题
// 寻找最小会议室数量(LeetCode 253)
sort(begin, end, [](auto& a, auto& b) { return a[0] < b[0]; });
priority_queue<int, vector<int>, greater<>> pq;

for (auto& interval : intervals) {
    if (!pq.empty() && pq.top() <= interval[0]) {
        pq.pop();
    }
    pq.push(interval[1]);
}
return pq.size();

3.9 二分查找的调试技巧

可视化调试法
template <typename T>
void print_interval(const vector<T>& vec, int l, int r) {
    cout << "当前区间: [";
    for (int i=l; i<=r; ++i) {
        cout << vec[i] << (i<r ? ", " : "");
    }
    cout << "]" << endl;
}

// 在循环体内插入:
print_interval(nums, l, r);
边界值测试用例
测试用例类型示例检查重点
空数组[]越界处理
单元素数组[5]边界条件
全相同元素[2,2,2,2]重复处理
目标在首尾[1,3,5], 找1或5端点判断
目标不存在[1,3,5], 找2或6返回值验证
超大数组1e8元素数组性能测试
浮点精度精确到1e-9的实数查找精度控制

3.10 二分查找的现代C++扩展

范围库(C++20)
#include <ranges>
vector<int> nums{1,3,5,7,9};

// 查找第一个偶数
auto it = ranges::lower_bound(nums, 0, {}, [](int x) {
    return x % 2 == 0 ? 0 : 1; // 投影函数
});
并行算法(C++17)
#include <execution>
vector<int> big_data(1e8);
// 并行排序(需要随机访问迭代器)
sort(execution::par, big_data.begin(), big_data.end());
auto pos = lower_bound(execution::par, big_data.begin(), big_data.end(), 42);

二分查找思维导图

二分查找
经典问题
STL算法
优化技巧
扩展应用
精确查找
范围统计
最大值最小化
lower_bound
upper_bound
binary_search
预处理+二分
答案二分法
STL组合使用
数学求根
离散化处理
时间安排

四、必须掌握的10个STL算法

1. reverse:序列倒置

函数原型
template <class BidirectionalIterator>
void reverse(BidirectionalIterator first, BidirectionalIterator last);
功能说明
  • 将区间[first, last)内的元素逆序排列
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {1,2,3,4,5};
reverse(vec.begin(), vec.end()); // 变为[5,4,3,2,1]

// 对比C实现
void reverse_c(int* arr, int n) {
    for(int i=0; i<n/2; i++) {
        swap(arr[i], arr[n-1-i]);
    }
}
适用场景
  • 回文串处理
  • 需要倒序输出的情况
  • 配合其他算法使用(如先排序后逆序)

2. unique:去除相邻重复元素

函数原型
template <class ForwardIterator>
ForwardIterator unique(ForwardIterator first, ForwardIterator last);
功能说明
  • 去除相邻的重复元素(通常先排序)
  • 返回值:新区间的尾后迭代器
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {1,1,2,2,3,3};
auto last = unique(vec.begin(), vec.end()); 
vec.erase(last, vec.end()); // 实际删除重复元素

// 对比C实现
int unique_c(int* arr, int n) {
    if(n == 0) return 0;
    int j=0;
    for(int i=1; i<n; i++) {
        if(arr[i] != arr[j]) arr[++j] = arr[i];
    }
    return j+1;
}
注意事项
  • 不会真正删除元素,只是移动元素位置
  • 必须配合排序使用才能完全去重
  • 典型用法:先sort后unique再erase

3. accumulate:序列求和

函数原型
template <class InputIterator, class T>
T accumulate(InputIterator first, InputIterator last, T init);
功能说明
  • 计算区间[first, last)的累加值
  • 初始值:init决定返回类型(重要!)
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {1,2,3,4,5};

// 求和(初始值为0)
int sum = accumulate(vec.begin(), vec.end(), 0); 

// 求积(初始值为1)
int product = accumulate(vec.begin(), vec.end(), 1, multiplies<int>());

// 拼接字符串
vector<string> words = {"Hello", "World"};
string concat = accumulate(words.begin(), words.end(), string(""));
进阶用法
  • 自定义运算规则(第四个参数)
  • 可用于非数值类型的累积操作

4. fill:区间填充

函数原型
template <class ForwardIterator, class T>
void fill(ForwardIterator first, ForwardIterator last, const T& val);
功能说明
  • 将区间[first, last)填充为指定值
  • 时间复杂度:O(n)
使用示例
vector<int> vec(10); // 10个0
fill(vec.begin(), vec.begin()+5, 42); // 前5个变为42

// 对比C实现
void fill_c(int* arr, int n, int val) {
    for(int i=0; i<n; i++) arr[i] = val;
}
注意事项
  • 与memset不同,fill按元素类型赋值
  • 适合初始化非POD类型(如string)

5. count:元素计数

函数原型
template <class InputIterator, class T>
typename iterator_traits<InputIterator>::difference_type
count(InputIterator first, InputIterator last, const T& val);
功能说明
  • 统计区间内等于val的元素个数
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {1,2,3,2,1};
int cnt = count(vec.begin(), vec.end(), 2); // 返回2

// 对比C实现
int count_c(int* arr, int n, int target) {
    int cnt = 0;
    for(int i=0; i<n; i++) 
        if(arr[i] == target) cnt++;
    return cnt;
}
进阶用法
  • 配合find实现"存在性检查+计数"组合操作

6. max_element:找最大值位置

函数原型
template <class ForwardIterator>
ForwardIterator max_element(ForwardIterator first, ForwardIterator last);
功能说明
  • 返回区间内最大元素的迭代器
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {3,1,4,2,5};
auto it = max_element(vec.begin(), vec.end());
cout << "最大值:" << *it << " 位置:" << it - vec.begin();

// 对比C实现
int* max_element_c(int* arr, int n) {
    if(n == 0) return NULL;
    int* max = arr;
    for(int i=1; i<n; i++)
        if(arr[i] > *max) max = &arr[i];
    return max;
}
注意事项
  • 返回的是迭代器(类似指针)
  • 空区间会导致未定义行为

7. rotate:区间旋转

函数原型
template <class ForwardIterator>
void rotate(ForwardIterator first, ForwardIterator middle, ForwardIterator last);
功能说明
  • 将区间[first, last)以middle为轴旋转
  • 效果[first,middle)移到末尾
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {1,2,3,4,5};
rotate(vec.begin(), vec.begin()+2, vec.end()); 
// 结果:3,4,5,1,2

// 典型应用:循环移位
void rotate_k(vector<int>& nums, int k) {
    k %= nums.size();
    rotate(nums.begin(), nums.end()-k, nums.end());
}
算法原理
  • 三次反转法实现高效旋转
  • 不需要额外空间

8. next_permutation:下一个排列

函数原型
template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last);
功能说明
  • 生成下一个字典序排列
  • 返回值:若存在返回true,否则返回false
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {1,2,3};
do {
    print(vec);
} while(next_permutation(vec.begin(), vec.end()));

/* 输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1 */
应用场景
  • 全排列生成
  • 组合数学问题
  • 密码爆破类题目

9. copy:复制序列

函数原型
template <class InputIterator, class OutputIterator>
OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);
功能说明
  • [first, last)复制到result起始的位置
  • 注意:确保目标区间足够空间
  • 时间复杂度:O(n)
使用示例
vector<int> src = {1,2,3};
vector<int> dest(3);
copy(src.begin(), src.end(), dest.begin());

// 配合插入迭代器
vector<int> dest2;
copy(src.begin(), src.end(), back_inserter(dest2));
高级技巧
  • 结合流迭代器实现快速IO
copy(vec.begin(), vec.end(), ostream_iterator<int>(cout, " "));

10. transform:元素转换

函数原型
template <class InputIterator, class OutputIterator, class UnaryOperation>
OutputIterator transform(InputIterator first1, InputIterator last1,
                         OutputIterator result, UnaryOperation op);
功能说明
  • 对区间每个元素应用操作,结果写入目标区间
  • 时间复杂度:O(n)
使用示例
vector<int> vec = {1,2,3};
vector<int> squared(vec.size());

// 平方运算
transform(vec.begin(), vec.end(), squared.begin(),
         [](int x) { return x*x; });

// 大小写转换
string s = "Hello";
transform(s.begin(), s.end(), s.begin(), ::toupper);
应用场景
  • 批量修改容器元素
  • 类型转换(配合static_cast)
  • 数学运算预处理

五、迭代器进阶:超越指针的智能导航

5.1 流迭代器:数据流与容器的无缝对接

输入流迭代器(istream_iterator)深度解析

底层实现原理

template<typename T, typename CharT = char>
class istream_iterator {
    istream* stream;
    T value;
    bool end_flag;

    void read() {
        if (stream && (*stream >> value)) end_flag = false;
        else { stream = nullptr; end_flag = true; }
    }
public:
    istream_iterator() : stream(nullptr), end_flag(true) {}
    istream_iterator(istream& s) : stream(&s) { read(); }

    const T& operator*() const { return value; }
    istream_iterator& operator++() { read(); return *this; }
    bool operator!=(const istream_iterator& other) const {
        return !(end_flag && other.end_flag);
    }
};

竞赛实战应用

// 从标准输入读取不定长整数
vector<int> data;
copy(istream_iterator<int>(cin), istream_iterator<int>(),
     back_inserter(data));

// 文件快速读取(配合ifstream)
ifstream fin("input.txt");
vector<double> values;
copy(istream_iterator<double>(fin), istream_iterator<double>(),
     back_inserter(values));

性能优化技巧

  1. 缓冲区设置:通过rdbuf()->pubsetbuf增大输入缓冲区
    char buf[1<<20];
    cin.rdbuf()->pubsetbuf(buf, sizeof(buf));
    
  2. 同步关闭:取消C风格IO同步
    ios::sync_with_stdio(false);
    
  3. 批量读取:结合vector::reserve预分配内存
    data.reserve(1e6); // 预分配百万级空间
    
输出流迭代器(ostream_iterator)高级用法

格式化输出示例

vector<int> vec{1,2,3,4,5};

// 基础输出
copy(vec.begin(), vec.end(), 
     ostream_iterator<int>(cout, " "));

// 格式化浮点数
vector<double> prices{9.99, 19.95};
cout << fixed << setprecision(2);
copy(prices.begin(), prices.end(), 
     ostream_iterator<double>(cout, "$ "));

// 自定义转换
transform(vec.begin(), vec.end(),
          ostream_iterator<string>(cout, "\n"),
          [](int x) { return to_string(x) + "kg"; });

文件高效写入

ofstream fout("output.txt");
vector<string> results;

// 直接写入文件(避免内存缓存)
copy(results.begin(), results.end(),
     ostream_iterator<string>(fout, "\n"));

5.2 逆向迭代器(reverse_iterator)的黑暗魔法

实现原理揭秘
template<class Iterator>
class reverse_iterator {
protected:
    Iterator current;
public:
    typedef Iterator iterator_type;

    explicit reverse_iterator(Iterator x) : current(x) {}

    reference operator*() const {
        Iterator tmp = current;
        return *--tmp;
    }

    reverse_iterator& operator++() {
        --current;
        return *this;
    }

    // 其他操作符重载...
};

典型应用场景

// 1. 逆序查找
string log = "Error:404|2023-08-20";
auto last_colon = find(rbegin(log), rend(log), ':');
if (last_colon != rend(log)) {
    cout << "最后出现的冒号位置:" 
         << (log.rend() - last_colon - 1);
}

// 2. 逆序排序
vector<int> data{5,3,9,1};
sort(data.rbegin(), data.rend()); // 9,5,3,1

// 3. 回文判断
string s = "madam";
bool is_palindrome = equal(s.begin(), s.begin()+s.size()/2, s.rbegin());

容器修改时的注意事项

vector<int> vec{1,2,3,4,5};
auto rit = vec.rbegin();

vec.push_back(6); // 可能使rit失效!
*rit = 10;       // 危险操作!

// 正确做法:修改后重新获取迭代器
vec.push_back(6);
rit = vec.rbegin();
*rit = 10; // 安全,此时rit指向新元素6的位置

5.3 插入迭代器:动态容器的智能扩展

三剑客特性对比
类型插入位置适用容器复杂度保证
back_insert_iterator尾部追加vector, deque等均摊O(1)
front_insert_iterator头部插入deque, list等O(1)
insert_iterator指定位置插入所有顺序容器O(n)

性能优化实战

// 错误示范:多次push_back导致反复扩容
vector<int> dest;
for (auto x : src) dest.push_back(x);

// 正确做法:预分配+back_inserter
dest.reserve(src.size());
copy(src.begin(), src.end(), back_inserter(dest));

高级组合技巧

// 1. 流式过滤转换
vector<int> src{1,2,3,4,5};
set<int> unique_data;

remove_copy_if(src.begin(), src.end(),
               inserter(unique_data, unique_data.end()),
               [](int x) { return x % 2 == 0; });

// 2. 多容器联合操作
deque<int> front_part{10,20};
list<int> back_part{30,40};

copy(front_part.begin(), front_part.end(),
     front_inserter(back_part));  // 头部插入

5.4 移动迭代器(C++17):资源转移的艺术

传统拷贝 vs 移动语义
class BigData {
    vector<int> data; // 包含大量数据的成员
public:
    BigData(BigData&& other) noexcept 
        : data(move(other.data)) {} // 移动构造函数
};

vector<BigData> source(1000);
vector<BigData> dest;

// 传统拷贝(性能灾难)
copy(source.begin(), source.end(), back_inserter(dest));

// 移动优化(高效转移资源)
move(source.begin(), source.end(), back_inserter(dest));

移动迭代器实战

vector<unique_ptr<int>> ptrs;
ptrs.push_back(make_unique<int>(42));

// 错误:无法拷贝unique_ptr
// vector<unique_ptr<int>> copies(ptrs.begin(), ptrs.end());

// 正确:使用移动迭代器
vector<unique_ptr<int>> moved;
copy(make_move_iterator(ptrs.begin()), 
     make_move_iterator(ptrs.end()),
     back_inserter(moved));

5.5 迭代器适配器:功能组合的瑞士军刀

过滤迭代器(概念实现)
template<typename Iterator, typename Predicate>
class filter_iterator {
    Iterator current, end;
    Predicate pred;

    void advance() {
        while (current != end && !pred(*current)) 
            ++current;
    }
public:
    filter_iterator(Iterator begin, Iterator end, Predicate p)
        : current(begin), end(end), pred(p) { advance(); }

    auto operator*() { return *current; }
    filter_iterator& operator++() { 
        ++current; 
        advance();
        return *this;
    }
    bool operator!=(const filter_iterator& other) {
        return current != other.current;
    }
};

// 使用示例
vector<int> nums{1,2,3,4,5};
auto even = [](int x) { return x%2 == 0; };
for (auto it = filter_iterator(nums.begin(), nums.end(), even); 
     it != filter_iterator(nums.end(), nums.end(), even); ++it) {
    cout << *it << " "; // 输出2 4
}
分块迭代器(竞赛实用)
// 处理二维数组的分块访问
vector<vector<int>> matrix(1000, vector<int>(1000));

auto chunk_view = views::transform(matrix, [](auto& row) {
    return views::chunk(row, 64); // 64元素为一块
});

for (auto& row_chunks : chunk_view) {
    for (auto chunk : row_chunks) {
        process_chunk(chunk);
    }
}

迭代器进阶性能优化表

场景传统方法迭代器优化法性能提升
大文件读取逐行读取+处理istream_iterator+算法3-5倍
逆序查找手动倒序遍历reverse_iterator代码量减少70%
容器合并循环+push_backinsert_iterator2倍
大对象转移深拷贝move_iterator100倍
条件过滤临时容器filter_iterator内存减少50%

六、竞赛实战:经典算法题重构

6.1 求数组最大值(对比C实现)

/* C语言版 */
int max_val = arr[0];
for(int i=1; i<n; i++) {
    if(arr[i] > max_val) max_val = arr[i];
}
// C++版
int max_val = *max_element(vec.begin(), vec.end());

6.2 逆序数组(对比C实现)

/* C语言版 */
void reverse(int* arr, int n) {
    for(int i=0; i<n/2; i++) {
        int temp = arr[i];
        arr[i] = arr[n-1-i];
        arr[n-1-i] = temp;
    }
}
// C++版
reverse(vec.begin(), vec.end());

6.3 数组去重(对比C实现)

/* C语言版 */
int unique(int* arr, int n) {
    if(n == 0) return 0;
    int j = 0;
    for(int i=1; i<n; i++) {
        if(arr[i] != arr[j]) {
            arr[++j] = arr[i];
        }
    }
    return j+1;
}
// C++版
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());

七、STL算法复杂度速查表

算法/操作平均时间复杂度最坏情况空间复杂度适用场景
sortO(n log n)O(n log n)O(log n)通用快速排序
stable_sortO(n log n)O(n log² n)O(n)需要稳定排序的场合
partial_sortO(n log k)O(n log k)O(log k)获取前k个有序元素
nth_elementO(n)O(n)O(1)快速找到第k大元素
lower_boundO(log n)O(log n)O(1)有序序列中找第一个≥target的元素
upper_boundO(log n)O(log n)O(1)有序序列中找第一个>target的元素
binary_searchO(log n)O(log n)O(1)判断元素是否存在
reverseO(n)O(n)O(1)逆序容器内容
uniqueO(n)O(n)O(1)去除相邻重复元素(需先排序)
accumulateO(n)O(n)O(1)序列求和/累积操作
fillO(n)O(n)O(1)区间填充固定值
countO(n)O(n)O(1)统计元素出现次数
max_elementO(n)O(n)O(1)查找最大值位置
min_elementO(n)O(n)O(1)查找最小值位置
rotateO(n)O(n)O(1)循环移位操作
next_permutationO(n)O(n)O(1)生成下一个排列
prev_permutationO(n)O(n)O(1)生成上一个排列
copyO(n)O(n)O(1)复制序列到目标位置
transformO(n)O(n)O(1)元素转换处理
replaceO(n)O(n)O(1)替换指定元素
findO(n)O(n)O(1)线性查找元素
generateO(n)O(n)O(1)生成序列
removeO(n)O(n)O(1)删除指定元素(逻辑删除)
shuffleO(n)O(n)O(1)随机打乱顺序
mergeO(n)O(n)O(n)合并两个有序序列
inplace_mergeO(n log n)O(n log n)O(1)原地合并有序区间
is_sortedO(n)O(n)O(1)判断是否已排序
partitionO(n)O(n)O(1)根据条件划分元素

关键要点解析:

  1. 排序算法选择原则

    • 数据量小时(n < 1000):插入排序更高效
    • 需要稳定性时:选择stable_sort
    • 只需要部分排序:partial_sortnth_element
  2. 查找算法对比

    查找需求
    是否有序?
    二分查找lower_bound/upper_bound
    线性查找find/count
  3. 复杂度陷阱警示

    • remove+erase组合:实际复杂度O(n²)(需要元素移动)
    • 嵌套使用算法:sort(unique(vec.begin(), vec.end()))实际是O(n log n + n)
  4. 空间复杂度说明

    • 标注O(1)的算法均为原地操作
    • stable_sort需要额外空间实现稳定性
    • merge需要目标容器预分配空间

八、竞赛算法选择决策树

是否需要排序?
├─ 是 → 是否需要稳定性?
│   ├─ 是 → stable_sort
│   └─ 否 → sort
├─ 否 → 是否需要查找?
│   ├─ 是 → 是否有序?
│   │   ├─ 是 → lower_bound/upper_bound
│   │   └─ 否 → find/count
│   └─ 否 → 是否需要转换?
│       ├─ 是 → transform
│       └─ 否 → 是否需要统计?
│           ├─ 是 → accumulate
│           └─ 否 → 结束
└─ 特殊需求?
    ├─ 需要排列 → next_permutation
    ├─ 需要合并 → merge/inplace_merge
    └─ 需要划分 → partition

下篇预告

第五篇:现代语法糖精粹

  • 范围for的底层实现原理
  • Lambda表达式的捕获列表详解
  • 结构化绑定解包黑科技
  • 移动语义在竞赛中的妙用

欢迎在评论区留下你在使用STL算法时遇到的难题~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值