6.27小学期基础语法记录:map和set实战、复杂字符串解析
需要专门复习的题目:
- prac1.19:关于自定义结构体的二分搜索,运用
lower_bound和upper_bound - prac1.20:二叉树前序、中序、后序遍历互换
📘 C++ STL 容器:map 与 set 实战与原理笔记
🧭 一、std::map 的核心特性
| 特性 | 描述 |
|---|---|
| 类型 | 红黑树(平衡二叉搜索树) |
| 排序方式 | 按 key 升序,除非自定义比较器 |
| 是否自动去重 | ✅ 是(基于 key) |
| 查找复杂度 | O(log n) |
| 访问元素 | map[key] 会自动插入默认值 |
| 元素唯一性依据 | !(comp(a,b)) && !(comp(b,a)) 为真时,a 与 b 等价,被视为“重复” |
🧩 二、自定义 map 排序规则(比较器)
🎯 需求:按 team[key] 中的积分、净胜球排序 map/set
std::map<std::string, std::pair<int, int>> team;
struct cmp {
bool operator()(const std::string &s, const std::string &t) const {
auto &a = team.at(s);
auto &b = team.at(t);
if (a.first != b.first) return a.first > b.first; // 积分高优先
if (a.second != b.second) return a.second > b.second; // 净胜球高优先
return s < t; // 字典序
}
};
🔥 注意事项:
- 比较器不能插入元素!使用
team.at()更安全(避免operator[]意外插入) - 比较器必须是严格弱序(strict weak ordering),不能简单
return false代表“相等”,否则容器行为未定义!
🔍 三、判断 map 中是否包含某个 key
| 方法 | 是否推荐 | 修改 map 吗 | 时间复杂度 | 说明 |
|---|---|---|---|---|
find(key) |
✅✅✅ | 否 | O(log n) | 返回迭代器,能访问值 |
count(key) |
✅✅ | 否 | O(log n) | 判断是否存在(返回 0/1) |
map[key] |
❌ | ✅(会插入) | O(log n) | 不建议用作“存在判断” |
♻️ 四、遍历 map
1. 范围 for 循环(推荐)
for (const auto& [k, v] : myMap) {
std::cout << k << " => " << v << '\n';
}
2. 迭代器遍历
for (auto it = myMap.begin(); it != myMap.end(); ++it) {
std::cout << it->first << " " << it->second << '\n';
}
3. 逆序遍历
for (auto it = myMap.rbegin(); it != myMap.rend(); ++it) {
// ...
}
✍️ 五、map 中添加新值
假设类型:map<string, pair<int, int>>
| 方法 | 说明 |
|---|---|
map[key] = {x, y}; |
自动插入或修改 |
insert({key, {x, y}}); |
插入新元素,若 key 存在失败 |
emplace(key, {x, y}); |
效率更高,原地构造 |
⚠️ 注意:
map[key].first += 5;是安全的,因为默认插入值为{0, 0}- 但对于更复杂类型(如
int*或结构体),要注意默认值是否合适
🥇 六、取 map 前两名元素
情况 1:按 key 升序取前两项(map 原始顺序)
int count = 0;
for (auto& [k, v] : myMap) {
if (++count > 2) break;
std::cout << k << " => " << v << '\n';
}
情况 2:按 value 降序排序取前两项
std::vector<std::pair<string, int>> vec(myMap.begin(), myMap.end());
std::sort(vec.begin(), vec.end(), [](auto& a, auto& b) {
return a.second > b.second;
});
for (int i = 0; i < 2 && i < vec.size(); ++i)
std::cout << vec[i].first << " " << vec[i].second << '\n';
🔄 七、清空 map
| 方法 | 是否推荐 | 是否释放内存 | 说明 |
|---|---|---|---|
myMap.clear(); |
✅✅✅ | ❌(未必释放全部内存) | 标准用法 |
myMap = map<...>(); |
✅ | ✅ | 重新构造,简洁 |
swap(myMap, map<...>()); |
✅✅ | ✅ | 更快释放内存 |
🎲 八、std::set 支持排序吗?能否去重?能否随机访问?
| 问题 | 答案 |
|---|---|
| 是否自动排序 | ✅ 默认按元素升序(<) |
| 是否允许重复元素 | ❌ 不允许(自动去重) |
| 是否支持自定义排序 | ✅ 支持传入比较器 |
| 是否支持随机访问(下标) | ❌ 不支持(是双向迭代器) |
| 访问第 k 个元素 | 用 advance(it, k),复杂度 O(k) |
| 是否能根据 value 去重 | ❌ 无法(去重依据是比较器返回“等价”) |
⚠️ 九、自定义比较器不去重的根本原因
比较器形式如:
struct cmp {
bool operator()(const string &s, const string &t) const {
auto a = team.at(s);
auto b = team.at(t);
if (a.first != b.first) return a.first > b.first;
if (a.second != b.second) return a.second > b.second;
return s < t;
}
};
✅ 正常排序,但不会去重的原因:
- 比较器最终使用
s < t区分队名 - 即使积分和净胜球相同,不同队名依然不等价
- 所以所有队伍都被保留,没有逻辑“重复”发生
❌ 若你试图强行返回 false 来表示相等:
if (积分和净胜球相等) return false; // 危险!
不再满足严格弱序,map/set 会行为未定义!
✅ 正确的去重方式(如果你真的只想保留一种积分 + 净胜球组合)
用 map<pair<int,int>, string> 或 unordered_map<pair<int,int>, string, 自定义哈希>:
map<pair<int, int>, string> uniqueTeam;
// key 为 (积分, 净胜球),值为队名,只保留一个
🧠 总结一张表
| 目标 | 推荐容器与方法 |
|---|---|
| 按 key 排序,自动去重 | std::map / std::set(默认) |
| 按 value 排序 | 拷贝到 vector + sort() |
| 自定义多字段排序 | 自定义比较器 struct |
| 想以积分+净胜球判断重复 | 改用 map<pair<int,int>, string> |
| 清空 map | clear() 或 swap() |
| 访问第 k 个元素 | 用迭代器 + advance(非随机访问) |
C++ 复杂格式字符串解析方法总结
概述
在处理类似 A-B 1:1 这种包含多种分隔符的复杂输入时,C++提供了多种解析方法,各有优缺点。
方法详解
1. scanf 风格解析 ⭐⭐⭐⭐⭐
原理:直接按格式模板匹配解析
char team1[31], team2[31];
int score1, score2;
scanf("%s-%s %d:%d", team1, team2, &score1, &score2);
// 如需string类型
string t1(team1), t2(team2);
特点:
- ✅ 效率最高:直接解析,无中间步骤
- ✅ 内存友好:无额外字符串分配
- ❌ C风格:需要预分配缓冲区
- ❌ 类型转换:需要手动转string
2. 字符串流 + 替换 ⭐⭐⭐⭐
原理:将分隔符统一替换为空格,利用流自动分割
string line;
getline(cin, line);
replace(line.begin(), line.end(), '-', ' ');
replace(line.begin(), line.end(), ':', ' ');
stringstream ss(line);
string team1, team2;
int score1, score2;
ss >> team1 >> team2 >> score1 >> score2;
特点:
- ✅ 简洁易懂:代码逻辑清晰
- ✅ 自动类型转换:流操作符自动处理
- ❌ 修改原串:破坏了原始数据
- ❌ 额外开销:替换操作 + 流构造
3. 位置查找 + 子串提取 ⭐⭐⭐
原理:找到分隔符位置,手动提取各部分
string line;
getline(cin, line);
int dash_pos = line.find('-');
int space_pos = line.find(' ');
int colon_pos = line.find(':');
string team1 = line.substr(0, dash_pos);
string team2 = line.substr(dash_pos + 1, space_pos - dash_pos - 1);
int score1 = stoi(line.substr(space_pos + 1, colon_pos - space_pos - 1));
int score2 = stoi(line.substr(colon_pos + 1));
特点:
- ✅ 保持原串:不修改原始数据
- ✅ 灵活控制:可精确控制提取范围
- ❌ 代码复杂:需要计算多个位置
- ❌ substr开销:多次内存分配
4. 正则表达式 ⭐⭐
原理:使用模式匹配提取信息
#include <regex>
string line;
getline(cin, line);
regex pattern(R"((\w+)-(\w+) (\d+):(\d+))");
smatch matches;
if(regex_match(line, matches, pattern)) {
string team1 = matches[1];
string team2 = matches[2];
int score1 = stoi(matches[3]);
int score2 = stoi(matches[4]);
}
特点:
- ✅ 最灵活:可处理复杂、变化的格式
- ✅ 表达力强:一个模式解决所有问题
- ❌ 性能最差:正则引擎开销巨大
- ❌ 编译开销:模式编译耗时
性能对比图
效率: scanf > 字符串流 > 位置查找 > 正则表达式
|||| ||| || |
100% 85% 60% 25%
使用场景建议
| 场景 | 推荐方法 | 理由 |
|---|---|---|
| 竞赛编程 | scanf | 效率优先,格式固定 |
| 工业代码 | 字符串流 | 可读性与效率平衡 |
| 格式变化 | 正则表达式 | 灵活性最重要 |
| 内存敏感 | 位置查找 | 避免额外分配 |
实际应用示例
处理多种比赛格式:
// 方法选择依据输入复杂度
if (format_is_fixed) {
// 使用 scanf
scanf("%s-%s %d:%d", team1, team2, &score1, &score2);
} else if (format_has_variations) {
// 使用正则表达式
regex pattern(R"((\w+)-(\w+)\s+(\d+):(\d+))");
}
记忆口诀:
- 速度王:scanf直接解析
- 平衡者:字符串流替换
- 控制狂:位置查找提取
- 万能钥匙:正则表达式

浙公网安备 33010602011771号