C++数据结构——Map

一、map 基础概念

1. 什么是 map

map 是 C++ STL 中的关联容器,它提供基于键(key)的快速数据检索能力,具有以下特性:

  • 键值对(key-value)存储
  • 按键自动排序(默认升序)
  • 键唯一(不允许重复)
  • 基于红黑树实现(平衡二叉搜索树)

2. map 基本操作复杂度

操作时间复杂度说明
插入O(log n)需要维持树平衡
删除O(log n)需要维持树平衡
查找O(log n)二分查找
遍历O(n)需要访问所有节点
修改值O(log n)需要先查找

二、map 的基本使用

1. 头文件与声明

#include <map>
#include <string>

// 基本声明
std::map<int, std::string> studentMap;

// 使用初始化列表
std::map<std::string, double> priceMap {
    {"apple", 1.99},
    {"banana", 0.99},
    {"orange", 2.49}
};

2. 常用操作详解

(1) 插入元素
// 使用insert成员函数
auto ret = studentMap.insert({101, "Alice"});
// ret是pair<iterator, bool>,second表示是否插入成功

// 使用operator[] (如果键不存在会自动创建)
studentMap[102] = "Bob"; // 插入或修改

// 使用emplace (C++11更高效的方式)
studentMap.emplace(103, "Charlie");
(2) 访问元素
// 使用operator[] (键不存在时会创建)
std::string name = studentMap[101];

// 使用at (键不存在时抛出out_of_range异常)
try {
    std::string name = studentMap.at(101);
} catch (const std::out_of_range& e) {
    std::cerr << "Key not found: " << e.what() << std::endl;
}

// 使用find (安全访问方式)
auto it = studentMap.find(104);
if (it != studentMap.end()) {
    std::cout << "Found: " << it->second << std::endl;
} else {
    std::cout << "Key 104 not found" << std::endl;
}
(3) 删除元素
// 通过键删除
size_t numRemoved = studentMap.erase(101);

// 通过迭代器删除
auto it = studentMap.find(102);
if (it != studentMap.end()) {
    studentMap.erase(it);
}

// 删除范围 (前闭后开区间)
auto first = studentMap.find(100);
auto last = studentMap.find(200);
if (first != studentMap.end() && last != studentMap.end()) {
    studentMap.erase(first, last);
}

// 清空map
studentMap.clear();
(4) 遍历元素
// 使用迭代器
for (auto it = studentMap.begin(); it != studentMap.end(); ++it) {
    std::cout << "ID: " << it->first << ", Name: " << it->second << std::endl;
}

// 使用范围for循环 (C++11)
for (const auto& pair : studentMap) {
    std::cout << "ID: " << pair.first << ", Name: " << pair.second << std::endl;
}

// 使用结构化绑定 (C++17)
for (const auto& [id, name] : studentMap) {
    std::cout << "ID: " << id << ", Name: " << name << std::endl;
}

三、map 的高级特性

1. 自定义比较函数

// 自定义键比较函数
struct CaseInsensitiveCompare {
    bool operator()(const std::string& a, const std::string& b) const {
        return std::lexicographical_compare(
            a.begin(), a.end(),
            b.begin(), b.end(),
            [](char c1, char c2) {
                return tolower(c1) < tolower(c2);
            });
    }
};

std::map<std::string, int, CaseInsensitiveCompare> wordCountMap;

// 使用函数指针
bool compareStrings(const std::string& a, const std::string& b) {
    return a.length() < b.length();
}

std::map<std::string, int, decltype(&compareStrings)> lengthMap(&compareStrings);

2. 特殊查找操作

std::map<int, std::string> m {
    {10, "ten"},
    {20, "twenty"},
    {30, "thirty"},
    {40, "forty"}
};

// lower_bound - 第一个不小于key的元素
auto lb = m.lower_bound(25); // 指向30

// upper_bound - 第一个大于key的元素
auto ub = m.upper_bound(25); // 指向30

// equal_range - 返回匹配的范围
auto range = m.equal_range(20);
// range.first指向20,range.second指向30

3. 性能优化技巧

// 1. 使用emplace_hint提高插入效率
auto hint = m.begin(); // 可以是任何合理的迭代器位置
m.emplace_hint(hint, 15, "fifteen");

// 2. 避免不必要的拷贝
std::map<int, std::string> bigMap;
// 不好:会创建临时string
bigMap[1] = "very long string...";
// 更好:使用try_emplace (C++17)
bigMap.try_emplace(1, "very long string...");

// 3. 节点操作 (C++17)
std::map<int, std::string> source, target;
auto node = source.extract(10); // 从source移除但不销毁
if (!node.empty()) {
    target.insert(std::move(node)); // 转移到target
}

四、map 的常见易错点

1. operator[] 的陷阱

std::map<std::string, int> wordCount;

// 以下操作会插入键"unknown"并值初始化为0
int count = wordCount["unknown"]; 

// 正确做法:先检查是否存在
auto it = wordCount.find("unknown");
if (it != wordCount.end()) {
    count = it->second;
}

2. 迭代器失效问题

std::map<int, int> m {{1, 10}, {2, 20}};

// 安全:删除当前迭代器指向的元素
for (auto it = m.begin(); it != m.end(); ) {
    if (it->second == 10) {
        it = m.erase(it); // C++11后erase返回下一个有效迭代器
    } else {
        ++it;
    }
}

// 危险:在遍历时插入元素可能导致树重新平衡,使迭代器失效

3. 自定义比较函数的严格要求

// 错误的比较函数:不满足严格弱序
struct BadCompare {
    bool operator()(int a, int b) const {
        return a <= b; // 错误!应该使用 <
    }
};

// 使用这种比较函数会导致未定义行为
std::map<int, int, BadCompare> badMap;

五、map 与其他容器的比较

1. map vs unordered_map

特性mapunordered_map
实现方式红黑树哈希表
元素顺序按键排序无序
查找复杂度O(log n)平均O(1),最差O(n)
内存使用通常较少通常较多(有负载因子)
适用场景需要有序遍历只需快速查找

2. map vs multimap

特性mapmultimap
键唯一性键唯一允许重复键
插入操作operator[]可用无operator[]
查找结果返回单个元素可能返回多个元素

六、实际应用案例

1. 单词统计

std::map<std::string, int> wordCount;
std::string word;

while (std::cin >> word) {
    // 使用operator[]会自动初始化新键为0
    ++wordCount[word];
}

// 输出统计结果(已按字典序排序)
for (const auto& [word, count] : wordCount) {
    std::cout << word << ": " << count << std::endl;
}

2. 学生成绩管理系统

class StudentGradeSystem {
private:
    std::map<int, std::map<std::string, double>> studentGrades;
    // 外层key: 学生ID
    // 内层map: 课程名 -> 成绩

public:
    void addGrade(int studentId, const std::string& course, double grade) {
        studentGrades[studentId][course] = grade;
    }

    double getGrade(int studentId, const std::string& course) const {
        auto studentIt = studentGrades.find(studentId);
        if (studentIt == studentGrades.end()) {
            throw std::runtime_error("Student not found");
        }

        auto courseIt = studentIt->second.find(course);
        if (courseIt == studentIt->second.end()) {
            throw std::runtime_error("Course not found for student");
        }

        return courseIt->second;
    }

    void printAllGrades() const {
        for (const auto& [id, courses] : studentGrades) {
            std::cout << "Student ID: " << id << "\n";
            for (const auto& [course, grade] : courses) {
                std::cout << "  " << course << ": " << grade << "\n";
            }
        }
    }
};

3. 区间映射(处理重叠区间)

class IntervalMap {
private:
    std::map<int, char> intervals; // key: 区间起点, value: 区间值

public:
    void addInterval(int start, int end, char value) {
        if (start >= end) return;

        // 删除或截断重叠区间
        auto it = intervals.upper_bound(start);
        if (it != intervals.begin()) {
            --it;
            if (it->second == value) {
                start = std::min(start, it->first);
            } else if (it->first + 1 > start) {
                // 截断现有区间
                intervals[start] = it->second;
                intervals[it->first] = it->second;
            }
            ++it;
        }

        // 处理结束部分
        while (it != intervals.end() && it->first < end) {
            if (it->second == value) {
                end = std::max(end, it->first + 1);
            } else {
                if (it->first + 1 > end) {
                    intervals[end] = it->second;
                }
            }
            it = intervals.erase(it);
        }

        intervals[start] = value;
    }

    char getValue(int point) const {
        auto it = intervals.upper_bound(point);
        if (it == intervals.begin()) {
            return '\0'; // 默认值
        }
        --it;
        return it->second;
    }
};

七、C++17/20 新特性

1. try_emplaceinsert_or_assign

std::map<int, std::string> m;

// try_emplace: 键不存在时才构造值
m.try_emplace(1, "one"); // 构造"one"
m.try_emplace(1, "uno"); // 无效果

// insert_or_assign: 插入或更新
m.insert_or_assign(1, "uno"); // 更新为"uno"

2. 节点操作

std::map<int, std::string> source, target;

// 从source提取节点(不分配/释放内存)
auto node = source.extract(1);

if (!node.empty()) {
    // 修改键
    node.key() = 2;
    
    // 插入到target
    target.insert(std::move(node));
}

3. contains 方法 (C++20)

std::map<int, std::string> m {{1, "one"}};

if (m.contains(1)) {
    std::cout << "Key 1 exists\n";
}

八、最佳实践总结

  1. 选择正确的容器

    • 需要有序遍历 → map
    • 只需快速查找 → unordered_map
    • 允许重复键 → multimap
  2. 安全访问

    • 优先使用 find + 检查 end()
    • 慎用 operator[](可能意外插入)
    • 使用 at() 进行带边界检查的访问
  3. 性能考虑

    • 批量插入时使用 emplace_hint
    • C++17+ 使用节点操作避免拷贝
    • 自定义比较函数要满足严格弱序
  4. 现代C++特性

    • 使用 try_emplace 避免不必要的临时对象
    • 使用结构化绑定简化遍历代码
    • C++20 使用 contains 替代 find + end 检查
  5. 线程安全

    • map 本身不是线程安全的
    • 多线程环境需要外部同步
    • 考虑使用 shared_mutex 实现读写锁

map 是 C++ 中最有用的容器之一,理解其内部实现和特性可以帮助你编写出更高效、更安全的代码。根据具体需求选择合适的变体(如 unordered_mapmultimap),并充分利用现代 C++ 提供的各种优化手段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值