这一章主要讲解关联容器,包括map、set、unordered_map、unordered_set等,这些容器提供了高效的查找、插入和删除操作。
11.1 使用关联容器
基本使用
#include <iostream>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <vector>
using std::cout;
using std::endl;
using std::string;
void mapUsage() {
cout << "=== map 基本使用 ===" << endl;
// 创建map
std::map<string, int> wordCount;
// 插入元素
wordCount["apple"] = 3;
wordCount["banana"] = 5;
wordCount.insert({"cherry", 2});
wordCount.insert(std::make_pair("date", 4));
// 访问元素
cout << "apple 出现次数: " << wordCount["apple"] << endl;
cout << "banana 出现次数: " << wordCount.at("banana") << endl;
// 遍历map
cout << "所有单词计数:" << endl;
for (const auto& pair : wordCount) {
cout << pair.first << ": " << pair.second << endl;
}
// 检查元素是否存在
if (wordCount.find("elderberry") == wordCount.end()) {
cout << "elderberry 不存在" << endl;
}
}
void setUsage() {
cout << "\n=== set 基本使用 ===" << endl;
// 创建set
std::set<int> numbers = {5, 2, 8, 1, 9, 3, 5, 2}; // 重复元素会被忽略
cout << "set中的数字: ";
for (int num : numbers) {
cout << num << " "; // 自动排序且去重
}
cout << endl;
// 插入元素
numbers.insert(4);
numbers.insert(7);
// 查找元素
auto it = numbers.find(5);
if (it != numbers.end()) {
cout << "找到数字: " << *it << endl;
}
// 删除元素
numbers.erase(2);
cout << "删除2后: ";
for (int num : numbers) cout << num << " ";
cout << endl;
}
void unorderedContainers() {
cout << "\n=== 无序容器 ===" << endl;
// unordered_map - 基于哈希表
std::unordered_map<string, string> capitalCities = {
{"China", "Beijing"},
{"USA", "Washington"},
{"Japan", "Tokyo"},
{"UK", "London"}
};
cout << "首都城市:" << endl;
for (const auto& pair : capitalCities) {
cout << pair.first << " -> " << pair.second << endl;
}
// unordered_set - 基于哈希表的集合
std::unordered_set<string> programmingLanguages = {
"C++", "Java", "Python", "JavaScript", "C++" // 重复元素被忽略
};
cout << "编程语言: ";
for (const auto& lang : programmingLanguages) {
cout << lang << " "; // 无序但唯一
}
cout << endl;
}
void multimapMultiset() {
cout << "\n=== 允许多个关键字的容器 ===" << endl;
// multimap - 允许重复key
std::multimap<string, string> authors;
authors.insert({"Bjarne", "C++ Programming"});
authors.insert({"Bjarne", "The C++ Language"});
authors.insert({"Scott", "Effective C++"});
authors.insert({"Scott", "More Effective C++"});
cout << "作者和书籍:" << endl;
for (const auto& pair : authors) {
cout << pair.first << ": " << pair.second << endl;
}
// multiset - 允许重复元素
std::multiset<int> scores = {85, 90, 85, 78, 90, 92, 85};
cout << "所有分数: ";
for (int score : scores) cout << score << " ";
cout << endl;
// 统计特定元素的个数
cout << "85分人数: " << scores.count(85) << endl;
}
int main() {
mapUsage();
setUsage();
unorderedContainers();
multimapMultiset();
return 0;
}
📊 容器对比表
| 特性 | std::multiset<int> | std::vector<int> | std::list<int> |
|---|---|---|---|
| 数据结构 | 红黑树 | 动态数组 | 双向链表 |
| 内存布局 | 非连续 | ✅ 连续 | 非连续 |
| 元素排序 | ✅ 自动排序 | ❌ 插入顺序 | ❌ 插入顺序 |
| 重复元素 | ✅ 允许重复 | ✅ 允许重复 | ✅ 允许重复 |
| 随机访问 | ❌ | ✅ O(1) | ❌ |
| 查找效率 | ✅ O(log n) | ✅ 已排序: O(log n), 未排序: O(n) | ❌ O(n) |
| 插入效率 | O(log n) | 尾部: ✅ O(1) | ✅ O(1) |
| 删除效率 | O(log n) | 尾部: ✅ O(1) | ✅ O(1) |
| 内存分配 | 逐个分配 | 批量分配 | 逐个分配 |
| 迭代器稳定性 | 插入删除可能失效 | 插入删除可能失效 | ✅ 稳定 |
| 缓存友好性 | ❌ | ✅ 优秀 | ❌ |
11.2 关联容器概述
容器类型和特点
#include <iostream>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <vector>
using std::cout;
using std::endl;
void containerCharacteristics() {
cout << "=== 关联容器特性 ===" << endl;
// 有序容器 (基于红黑树)
std::map<int, string> orderedMap;
std::set<int> orderedSet;
// 无序容器 (基于哈希表)
std::unordered_map<int, string> unorderedMap;
std::unordered_set<int> unorderedSet;
cout << "有序容器特点:" << endl;
cout << "- 按键排序" << endl;
cout << "- 支持范围查询" << endl;
cout << "- 插入/删除/查找: O(log n)" << endl;
cout << "\n无序容器特点:" << endl;
cout << "- 不排序,基于哈希" << endl;
cout << "- 平均情况O(1),最坏情况O(n)" << endl;
cout << "- 需要哈希函数" << endl;
}
void pairType() {
cout << "\n=== pair 类型 ===" << endl;
// pair的创建和初始化
std::pair<string, int> student1("张三", 85);
std::pair<string, int> student2 = {"李四", 92};
auto student3 = std::make_pair("王五", 78);
// 访问pair成员
cout << student1.first << " 分数: " << student1.second << endl;
cout << student2.first << " 分数: " << student2.second << endl;
cout << student3.first << " 分数: " << student3.second << endl;
// pair的比较
std::pair<int, int> p1(1, 2);
std::pair<int, int> p2(1, 3);
std::pair<int, int> p3(2, 1);
cout << "p1 < p2: " << (p1 < p2) << endl; // true, 第二个元素比较
cout << "p1 < p3: " << (p1 < p3) << endl; // true, 第一个元素比较
}
void keyTypeRequirements() {
cout << "\n=== 关键字类型要求 ===" << endl;
// 有序容器要求关键字类型支持 < 操作
std::map<string, int> validMap; // string 支持 <
// 自定义类型作为关键字
struct Point {
int x, y;
// 需要定义比较操作
bool operator<(const Point& other) const {
if (x != other.x) return x < other.x;
return y < other.y;
}
};
std::map<Point, string> pointMap;
pointMap[{1, 2}] = "A";
pointMap[{3, 4}] = "B";
pointMap[{1, 3}] = "C";
cout << "点映射:" << endl;
for (const auto& pair : pointMap) {
cout << "(" << pair.first.x << "," << pair.first.y << ") -> "
<< pair.second << endl;
}
// 无序容器要求关键字类型支持 == 和哈希函数
struct Person {
string name;
int age;
// 相等比较
bool operator==(const Person& other) const {
return name == other.name && age == other.age;
}
};
// 哈希函数对象
struct PersonHash {
std::size_t operator()(const Person& p) const {
// 注意这里的写法
return std::hash<string>()(p.name) ^
(std::hash<int>()(p.age) << 1);
}
};
std::unordered_map<Person, string, PersonHash> personMap;
personMap[{"张三", 25}] = "工程师";
personMap[{"李四", 30}] = "设计师";
cout << "\n人员映射:" << endl;
for (const auto& pair : personMap) {
cout << pair.first.name << "(" << pair.first.age << ") -> "
<< pair.second << endl;
}
}
int main() {
containerCharacteristics();
pairType();
keyTypeRequirements();
return 0;
}
11.3 关联容器操作
关联容器迭代器
#include <iostream>
#include <map>
#include <set>
#include <string>
using std::cout;
using std::endl;
using std::string;
void associativeIterators() {
cout << "=== 关联容器迭代器 ===" << endl;
std::map<string, int> wordCount = {
{"apple", 5},
{"banana", 3},
{"cherry", 8},
{"date", 2}
};
// map迭代器指向pair<const key_type, mapped_type>
cout << "遍历map:" << endl;
for (auto it = wordCount.begin(); it != wordCount.end(); ++it) {
// it->first 是 const,不能修改
// it->second 可以修改
cout << it->first << ": " << it->second;
it->second *= 2; // 可以修改值
cout << " -> " << it->second << endl;
}
// set迭代器是const的
std::set<int> numbers = {1, 3, 5, 7, 9};
cout << "\n遍历set:" << endl;
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
// *it = 10; // 错误!set元素是const
cout << *it << " ";
}
cout << endl;
// 反向迭代器
cout << "反向遍历map:" << endl;
for (auto rit = wordCount.rbegin(); rit != wordCount.rend(); ++rit) {
cout << rit->first << ": " << rit->second << endl;
}
}
void addingElements() {
cout << "\n=== 添加元素 ===" << endl;
std::map<string, int> scores;
// 1. insert 方法
auto ret1 = scores.insert({"张三", 85});
cout << "插入结果: " << ret1.second
<< ", 迭代器指向: " << ret1.first->first << endl;
// 插入已存在元素
auto ret2 = scores.insert({"张三", 90}); // 不会更新
cout << "重复插入结果: " << ret2.second << endl;
cout << "张三分数: " << scores["张三"] << endl; // 仍然是85
// 2. emplace 插入方法 (C++11)
auto ret3 = scores.emplace("李四", 92);
cout << "emplace结果: " << ret3.second << endl;
// 3. 下标操作 - 如果不存在会插入
scores["王五"] = 78; // 插入新元素
scores["张三"] = 95; // 更新已存在元素
cout << "\n所有分数:" << endl;
for (const auto& pair : scores) {
cout << pair.first << ": " << pair.second << endl;
}
}
void accessingElements() {
cout << "\n=== 访问元素 ===" << endl;
std::map<string, int> inventory = {
{"apple", 10},
{"banana", 20},
{"cherry", 15}
};
// 1. 下标操作 - 如果key不存在会插入
cout << "apple库存: " << inventory["apple"] << endl;
cout << "新增orange库存: " << inventory["orange"] << endl; // 插入默认值0
// 2. at方法 - 如果key不存在抛出异常
try {
cout << "banana库存: " << inventory.at("banana") << endl;
cout << "grape库存: " << inventory.at("grape") << endl; // 抛出异常
} catch (const std::out_of_range& e) {
cout << "异常: " << e.what() << endl;
}
// 3. find方法 - 安全访问
auto it = inventory.find("cherry");
if (it != inventory.end()) {
cout << "cherry库存: " << it->second << endl;
} else {
cout << "cherry不存在" << endl;
}
auto it2 = inventory.find("mango");
if (it2 == inventory.end()) {
cout << "mango不存在" << endl;
}
}
void removingElements() {
cout << "\n=== 删除元素 ===" << endl;
std::map<int, string> students = {
{1001, "张三"},
{1002, "李四"},
{1003, "王五"},
{1004, "赵六"},
{1005, "钱七"}
};
cout << "删除前:" << endl;
for (const auto& pair : students) {
cout << pair.first << ": " << pair.second << endl;
}
// 1. erase by key
size_t count = students.erase(1002);
cout << "\n删除了 " << count << " 个元素" << endl;
// 2. erase by iterator
auto it = students.find(1004);
if (it != students.end()) {
students.erase(it);
}
// 3. erase range
students.erase(students.find(1005), students.end());
cout << "\n删除后:" << endl;
for (const auto& pair : students) {
cout << pair.first << ": " << pair.second << endl;
}
}
int main() {
associativeIterators();
addingElements();
accessingElements();
removingElements();
return 0;
}
11.4 无序容器
哈希管理和桶操作
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <vector>
using std::cout;
using std::endl;
using std::string;
void unorderedContainerBasics() {
cout << "=== 无序容器基础 ===" << endl;
std::unordered_map<string, int> wordCount;
// 插入元素
wordCount["apple"] = 5;
wordCount["banana"] = 3;
wordCount["cherry"] = 8;
wordCount["date"] = 2;
wordCount["elderberry"] = 6;
// 遍历无序容器
cout << "单词计数:" << endl;
for (const auto& pair : wordCount) {
cout << pair.first << ": " << pair.second << endl;
}
// 桶接口
cout << "\n桶信息:" << endl;
cout << "桶数量: " << wordCount.bucket_count() << endl;
cout << "最大桶数量: " << wordCount.max_bucket_count() << endl;
cout << "元素数量: " << wordCount.size() << endl;
cout << "负载因子: " << wordCount.load_factor() << endl;
cout << "最大负载因子: " << wordCount.max_load_factor() << endl;
}
void bucketOperations() {
cout << "\n=== 桶操作 ===" << endl;
std::unordered_set<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 查看桶信息
cout << "数字集合桶分布:" << endl;
for (size_t i = 0; i < numbers.bucket_count(); ++i) {
cout << "桶 " << i << " 有 " << numbers.bucket_size(i) << " 个元素: ";
for (auto it = numbers.begin(i); it != numbers.end(i); ++it) {
cout << *it << " ";
}
cout << endl;
}
// 特定元素的桶位置
for (int num : {3, 7, 11}) {
if (numbers.find(num) != numbers.end()) {
cout << "数字 " << num << " 在桶 " << numbers.bucket(num) << endl;
} else {
cout << "数字 " << num << " 不存在" << endl;
}
}
}
void hashManagement() {
cout << "\n=== 哈希管理 ===" << endl;
std::unordered_map<string, int> map;
// 设置最大负载因子
map.max_load_factor(0.7f);
cout << "最大负载因子: " << map.max_load_factor() << endl;
// 预分配桶
map.reserve(50); // 确保至少能容纳50个元素而不重组
cout << "预分配后桶数量: " << map.bucket_count() << endl;
// 添加一些元素
for (int i = 0; i < 20; ++i) {
map["key" + std::to_string(i)] = i;
}
cout << "添加元素后桶数量: " << map.bucket_count() << endl;
cout << "当前负载因子: " << map.load_factor() << endl;
// 重组哈希表
map.rehash(100);
cout << "重组后桶数量: " << map.bucket_count() << endl;
}
void customHashFunction() {
cout << "\n=== 自定义哈希函数 ===" << endl;
struct Point {
int x, y;
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
// 自定义哈希函数
struct PointHash {
std::size_t operator()(const Point& p) const {
// 简单哈希组合,第二个参数左移1位,因为如果两个参数hash值相等,结果为零,容易冲突
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
// 使用自定义哈希函数的unordered_set
std::unordered_set<Point, PointHash> points;
points.insert({1, 2});
points.insert({3, 4});
points.insert({1, 2}); // 重复,不会被插入
cout << "点集合:" << endl;
for (const auto& p : points) {
cout << "(" << p.x << ", " << p.y << ")" << endl;
}
// 更复杂的哈希函数示例
struct Person {
string firstName;
string lastName;
int age;
bool operator==(const Person& other) const {
return firstName == other.firstName &&
lastName == other.lastName &&
age == other.age;
}
};
struct PersonHash {
std::size_t operator()(const Person& p) const {
return std::hash<string>()(p.firstName) ^
(std::hash<string>()(p.lastName) << 1) ^
(std::hash<int>()(p.age) << 2);
}
};
std::unordered_map<Person, string, PersonHash> personJob;
personJob[{"John", "Doe", 30}] = "Engineer";
personJob[{"Jane", "Smith", 25}] = "Designer";
cout << "\n人员职业:" << endl;
for (const auto& pair : personJob) {
const auto& p = pair.first;
cout << p.firstName << " " << p.lastName << " (" << p.age << ") -> "
<< pair.second << endl;
}
}
int main() {
unorderedContainerBasics();
bucketOperations();
hashManagement();
customHashFunction();
return 0;
}
综合示例:单词统计系统
#include <iostream>
#include <map>
#include <unordered_map>
#include <set>
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
#include <cctype>
using std::cout;
using std::endl;
using std::string;
using std::vector;
class WordCounter {
private:
std::map<string, int> wordCount; // 按字母顺序排序
std::unordered_map<string, int> wordHash; // 快速查找
std::set<string> stopWords; // 停用词
public:
WordCounter() {
// 初始化停用词
stopWords = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of"};
}
// 清理和规范化单词
string normalizeWord(const string& word) {
string result;
for (char c : word) {
if (std::isalpha(c)) {
result += std::tolower(c);
}
}
return result;
}
// 处理文本
void processText(const string& text) {
std::istringstream iss(text);
string word;
while (iss >> word) {
string normalized = normalizeWord(word);
if (!normalized.empty() && stopWords.find(normalized) == stopWords.end()) {
// 更新两个容器
wordCount[normalized]++;
wordHash[normalized]++;
}
}
}
// 显示单词统计(按字母顺序)
void displayAlphabetical() const {
cout << "=== 按字母顺序的单词统计 ===" << endl;
for (const auto& pair : wordCount) {
cout << pair.first << ": " << pair.second << endl;
}
}
// 显示单词统计(按频率排序)
void displayByFrequency() const {
cout << "\n=== 按频率排序的单词统计 ===" << endl;
// 使用vector排序
vector<std::pair<string, int>> sortedWords(wordCount.begin(), wordCount.end());
std::sort(sortedWords.begin(), sortedWords.end(),
[](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // 频率高的在前
}
return a.first < b.first; // 频率相同按字母顺序
});
for (const auto& pair : sortedWords) {
cout << pair.first << ": " << pair.second << endl;
}
}
// 查找特定单词
void findWord(const string& word) const {
string normalized = normalizeWord(word);
// 使用unordered_map快速查找
auto it = wordHash.find(normalized);
if (it != wordHash.end()) {
cout << "找到 '" << normalized << "': 出现 " << it->second << " 次" << endl;
} else {
cout << "未找到 '" << normalized << "'" << endl;
}
}
// 获取最常用的单词
vector<string> getMostFrequent(int n) const {
vector<std::pair<string, int>> sortedWords(wordCount.begin(), wordCount.end());
std::sort(sortedWords.begin(), sortedWords.end(),
[](const auto& a, const auto& b) {
return a.second > b.second;
});
vector<string> result;
for (int i = 0; i < n && i < sortedWords.size(); ++i) {
result.push_back(sortedWords[i].first);
}
return result;
}
// 获取统计信息
void showStatistics() const {
cout << "\n=== 统计信息 ===" << endl;
cout << "总单词数: " << wordCount.size() << endl;
if (wordCount.empty()) return;
// 使用算法计算统计信息
int totalOccurrences = 0;
for (const auto& pair : wordCount) {
totalOccurrences += pair.second;
}
auto maxIt = std::max_element(wordCount.begin(), wordCount.end(),
[](const auto& a, const auto& b) {
return a.second < b.second;
});
auto minIt = std::min_element(wordCount.begin(), wordCount.end(),
[](const auto& a, const auto& b) {
return a.second < b.second;
});
cout << "总出现次数: " << totalOccurrences << endl;
cout << "最频繁单词: '" << maxIt->first << "' (" << maxIt->second << "次)" << endl;
cout << "最不频繁单词: '" << minIt->first << "' (" << minIt->second << "次)" << endl;
cout << "平均频率: " << static_cast<double>(totalOccurrences) / wordCount.size() << endl;
}
// 获取特定频率范围的单词
vector<string> getWordsInRange(int minFreq, int maxFreq) const {
vector<string> result;
for (const auto& pair : wordCount) {
if (pair.second >= minFreq && pair.second <= maxFreq) {
result.push_back(pair.first);
}
}
return result;
}
};
int main() {
WordCounter counter;
// 示例文本
string text = R"(
The quick brown fox jumps over the lazy dog.
The dog barked at the fox, but the fox kept running.
Quick and brown, the fox was very quick indeed.
Programming in C++ is fun and challenging.
C++ programming requires practice and dedication.
)";
// 处理文本
counter.processText(text);
// 显示结果
counter.displayAlphabetical();
counter.displayByFrequency();
// 统计信息
counter.showStatistics();
// 查找单词
cout << "\n=== 单词查找 ===" << endl;
counter.findWord("fox");
counter.findWord("quick");
counter.findWord("python");
// 获取最常用单词
auto topWords = counter.getMostFrequent(3);
cout << "\n最常用的3个单词: ";
for (const auto& word : topWords) {
cout << word << " ";
}
cout << endl;
// 获取频率范围内的单词
auto rangeWords = counter.getWordsInRange(2, 3);
cout << "频率2-3次的单词: ";
for (const auto& word : rangeWords) {
cout << word << " ";
}
cout << endl;
return 0;
}
📝 第十一章关键要点总结
-
关联容器类型:
- 有序容器:
map,set,multimap,multiset(基于红黑树) - 无序容器:
unordered_map,unordered_set等(基于哈希表)
- 有序容器:
-
关键字要求:
- 有序容器:必须定义
<操作或提供比较函数 - 无序容器:必须定义
==操作和哈希函数
- 有序容器:必须定义
-
主要操作:
- 插入:
insert,emplace, 下标操作 - 访问:
at,find, 下标操作 - 删除:
erase - 遍历:迭代器
- 插入:
-
性能特点:
- 有序容器:O(log n) 的查找、插入、删除
- 无序容器:平均O(1),最坏O(n)的操作
-
特殊容器:
multimap/multiset:允许多个相同关键字unordered_map/unordered_set:基于哈希,不保持顺序
关联容器是C++中非常强大和实用的工具,特别适合需要快速查找和唯一性保证的场景。多练习这些容器的使用,理解它们的内部原理和适用场景!
840

被折叠的 条评论
为什么被折叠?



