系列导航:
- [第一篇] 基础语法与竞赛优势
- [第二篇] 动态数组与字符串革命
- [第三篇] 映射与集合的终极形态
- [▶ 本篇] STL算法与迭代器
- [第五篇] 现代语法糖精粹
- [第六篇] 竞赛实战技巧
一、迭代器:从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); // 交换前两个元素
迭代器失效的七大场景(必知!)
容器类型 | 修改操作 | 失效规则 |
---|---|---|
vector | push_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) | 内存占用 |
---|---|---|
下标访问 | 12 | 3.8MB |
迭代器访问 | 13 | 3.8MB |
range-based for | 13 | 3.8MB |
逆向迭代器 | 15 | 3.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
迭代器思维导图
二、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) |
---|---|---|
qsort | 120 | 3.8 |
sort | 82 | 3.8 |
性能优势的关键因素:
- 内联优化:sort的比较操作在编译期确定
- 模板特化:对基础类型采用优化算法
- 减少间接调用:避免qsort的函数指针跳转
- 混合排序策略:结合快速排序+插入排序+堆排序
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的位置
关键特性要求
- 有序性:必须单调递增/递减
- 确定性:比较结果能明确划分区间
- 边界处理:开闭区间的一致性
- 终止条件:left <= right 或 left < right
3.2 STL二分三剑客深度解析
函数对比表(重点记忆)
函数名 | 返回值条件 | 等效表达式 | 典型应用场景 |
---|---|---|---|
lower_bound | 第一个 >= target | arr[i] >= target | 求元素存在性/插入位置 |
upper_bound | 第一个 > target | arr[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) | 代码复杂度 |
---|---|---|---|
手写循环 | 320 | 1.2 | 高 |
STL lower_bound | 280 | 1.5 | 低 |
递归实现 | 450 | 2.1 | 中 |
性能优势分析:
- 内联优化:STL模板在编译期展开
- 分支预测:STL使用前进次数计算减少分支
- 指令级优化:编译器对STL代码优化更充分
- 缓存友好:迭代器访问模式更规律
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);
二分查找思维导图
四、必须掌握的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));
性能优化技巧:
- 缓冲区设置:通过
rdbuf()->pubsetbuf
增大输入缓冲区char buf[1<<20]; cin.rdbuf()->pubsetbuf(buf, sizeof(buf));
- 同步关闭:取消C风格IO同步
ios::sync_with_stdio(false);
- 批量读取:结合
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_back | insert_iterator | 2倍 |
大对象转移 | 深拷贝 | move_iterator | 100倍 |
条件过滤 | 临时容器 | 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算法复杂度速查表
算法/操作 | 平均时间复杂度 | 最坏情况 | 空间复杂度 | 适用场景 |
---|---|---|---|---|
sort | O(n log n) | O(n log n) | O(log n) | 通用快速排序 |
stable_sort | O(n log n) | O(n log² n) | O(n) | 需要稳定排序的场合 |
partial_sort | O(n log k) | O(n log k) | O(log k) | 获取前k个有序元素 |
nth_element | O(n) | O(n) | O(1) | 快速找到第k大元素 |
lower_bound | O(log n) | O(log n) | O(1) | 有序序列中找第一个≥target的元素 |
upper_bound | O(log n) | O(log n) | O(1) | 有序序列中找第一个>target的元素 |
binary_search | O(log n) | O(log n) | O(1) | 判断元素是否存在 |
reverse | O(n) | O(n) | O(1) | 逆序容器内容 |
unique | O(n) | O(n) | O(1) | 去除相邻重复元素(需先排序) |
accumulate | O(n) | O(n) | O(1) | 序列求和/累积操作 |
fill | O(n) | O(n) | O(1) | 区间填充固定值 |
count | O(n) | O(n) | O(1) | 统计元素出现次数 |
max_element | O(n) | O(n) | O(1) | 查找最大值位置 |
min_element | O(n) | O(n) | O(1) | 查找最小值位置 |
rotate | O(n) | O(n) | O(1) | 循环移位操作 |
next_permutation | O(n) | O(n) | O(1) | 生成下一个排列 |
prev_permutation | O(n) | O(n) | O(1) | 生成上一个排列 |
copy | O(n) | O(n) | O(1) | 复制序列到目标位置 |
transform | O(n) | O(n) | O(1) | 元素转换处理 |
replace | O(n) | O(n) | O(1) | 替换指定元素 |
find | O(n) | O(n) | O(1) | 线性查找元素 |
generate | O(n) | O(n) | O(1) | 生成序列 |
remove | O(n) | O(n) | O(1) | 删除指定元素(逻辑删除) |
shuffle | O(n) | O(n) | O(1) | 随机打乱顺序 |
merge | O(n) | O(n) | O(n) | 合并两个有序序列 |
inplace_merge | O(n log n) | O(n log n) | O(1) | 原地合并有序区间 |
is_sorted | O(n) | O(n) | O(1) | 判断是否已排序 |
partition | O(n) | O(n) | O(1) | 根据条件划分元素 |
关键要点解析:
-
排序算法选择原则
- 数据量小时(n < 1000):插入排序更高效
- 需要稳定性时:选择
stable_sort
- 只需要部分排序:
partial_sort
或nth_element
-
查找算法对比
-
复杂度陷阱警示
remove
+erase
组合:实际复杂度O(n²)(需要元素移动)- 嵌套使用算法:
sort(unique(vec.begin(), vec.end()))
实际是O(n log n + n)
-
空间复杂度说明
- 标注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算法时遇到的难题~