6.27小学期基础语法记录:map和set实战、复杂字符串解析

需要专门复习的题目:

  • prac1.19:关于自定义结构体的二分搜索,运用lower_boundupper_bound
  • prac1.20:二叉树前序、中序、后序遍历互换

📘 C++ STL 容器:mapset 实战与原理笔记


🧭 一、std::map 的核心特性

特性 描述
类型 红黑树(平衡二叉搜索树)
排序方式 按 key 升序,除非自定义比较器
是否自动去重 ✅ 是(基于 key)
查找复杂度 O(log n)
访问元素 map[key] 会自动插入默认值
元素唯一性依据 !(comp(a,b)) && !(comp(b,a)) 为真时,ab 等价,被视为“重复”

🧩 二、自定义 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直接解析
  • 平衡者:字符串流替换
  • 控制狂:位置查找提取
  • 万能钥匙:正则表达式
posted @ 2025-06-27 21:37  十八Eigh18n  阅读(23)  评论(0)    收藏  举报