目录
引言
在 C++ 标准模板库 (STL) 中,map
和 set
是两种非常有用的容器,它们提供了高效的键值对存储和集合操作能力。map
用于存储键值对,而 set
用于存储唯一键的集合。这两种容器都基于红黑树实现,这意味着它们的大多数操作(如查找、插入和删除)的时间复杂度都是 O(log N)。本文将介绍 map
和 set
的基本用法以及在实际编程中的应用案例。
基础知识
map
map
是一个关联容器,用于存储键值对。键必须是唯一的,而值可以是任何类型。map
的元素按照键的升序排序。
示例代码
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> fruits;
fruits["apple"] = 3;
fruits["banana"] = 2;
fruits["cherry"] = 5;
for (const auto& fruit : fruits) {
std::cout << fruit.first << ": " << fruit.second << std::endl;
}
return 0;
}
set
set
也是一个关联容器,但它只存储唯一的键。这些键默认按升序排序。
示例代码
#include <iostream>
#include <set>
int main() {
std::set<int> numbers;
numbers.insert(3);
numbers.insert(1);
numbers.insert(2);
numbers.insert(4);
for (const auto& number : numbers) {
std::cout << number << " ";
}
std::cout << std::endl;
return 0;
}
应用场景
词频统计
词频统计是自然语言处理中的一项常见任务。map
可以用来高效地统计文本中单词的出现频率。
示例代码
#include <iostream>
#include <map>
#include <sstream>
#include <string>
int main() {
// 定义一个字符串,用于存储文本
std::string text = "The quick brown fox jumps over the lazy dog";
// 使用istringstream来分割文本中的单词
std::istringstream iss(text);
// 定义一个map,用于存储单词及其出现的次数
std::map<std::string, int> wordCount;
// 读取文本中的每一个单词
std::string word;
while (iss >> word) {
// 对于每一个单词,将其出现次数加一
++wordCount[word];
}
// 输出每个单词及其出现的次数
for (const auto& pair : wordCount) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
去重和排序
set
可以用来去重并自动排序一组数据。
示例代码
#include <iostream>
#include <set>
#include <vector>
int main() {
// 定义一个包含重复数字的向量
std::vector<int> numbers = {4, 1, 2, 4, 3, 2, 1};
// 使用set来去重并排序这些数字
std::set<int> uniqueNumbers(numbers.begin(), numbers.end());
// 输出去重并排序后的数字
for (const auto& number : uniqueNumbers) {
std::cout << number << " ";
}
std::cout << std::endl;
return 0;
}
动态排名
在比赛中,经常需要实时更新参赛者的排名。map
可以用来存储参赛者及其得分,并根据得分进行排序。
示例代码
#include <iostream>
#include <map>
#include <string>
int main() {
// 定义一个map,用于存储参赛者的名字和他们的分数
std::map<std::string, int> scores;
scores["Alice"] = 100;
scores["Bob"] = 80;
scores["Charlie"] = 90;
// 输出每个参赛者的名字和分数
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
图算法中的应用
在图算法中,set
和 map
都是非常有用的工具。例如,在 Dijkstra 算法中,set
可以用来维护待处理的顶点集合,而 map
可以用来存储顶点到源顶点的距离。
示例代码
#include <iostream>
#include <map>
#include <set>
#include <utility>
// 定义边的数据结构
struct Edge {
int destination; // 目的地顶点
int weight; // 边的权重
};
int main() {
// 定义一个图,使用map来存储每个顶点及其相邻的顶点和权重
std::map<int, std::vector<Edge>> graph;
graph[0] = {{1, 1}, {2, 4}}; // 顶点0连接顶点1和2
graph[1] = {{2, 2}}; // 顶点1连接顶点2
graph[2] = {}; // 顶点2没有出边
// 定义一个map,用于存储每个顶点到源顶点的距离
std::map<int, int> distance;
// 定义一个set,用于存储待处理的顶点
std::set<std::pair<int, int>> unvisited;
unvisited.insert({0, 0}); // 将源顶点0及其距离0加入待处理顶点集
distance[0] = 0; // 源顶点到自身的距离为0
// Dijkstra算法的主要循环
while (!unvisited.empty()) {
// 获取待处理顶点集中距离最短的顶点
auto minDist = *unvisited.begin();
unvisited.erase(unvisited.begin()); // 从待处理顶点集中移除该顶点
int currentNode = minDist.second; // 获取当前顶点
// 遍历当前顶点的所有出边
for (const auto& edge : graph[currentNode]) {
int newDistance = distance[currentNode] + edge.weight; // 计算新距离
if (newDistance < distance[edge.destination]) { // 如果新距离更短
distance[edge.destination] = newDistance; // 更新距离
unvisited.insert({newDistance, edge.destination}); // 加入待处理顶点集
}
}
}
// 输出每个顶点到源顶点的距离
for (const auto& pair : distance) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
高级技巧
自定义比较器
map
和 set
支持自定义比较器,这样可以根据特定需求对元素进行排序。
示例代码
#include <iostream>
#include <set>
#include <string>
// 定义一个比较器,用于根据字符串的长度进行比较
struct CustomComparator {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return lhs.length() < rhs.length(); // 如果左侧字符串长度小于右侧,则返回true
}
};
int main() {
// 定义一个set,使用自定义的比较器
std::set<std::string, CustomComparator> words;
words.insert("the");
words.insert("quick");
words.insert("brown");
words.insert("fox");
// 输出按照字符串长度排序后的单词
for (const auto& word : words) {
std::cout << word << " ";
}
std::cout << std::endl;
return 0;
}
多值映射
multimap
是 map
的变体,它允许相同的键对应多个值。
示例代码
#include <iostream>
#include <multimap>
#include <string>
int main() {
// 定义一个多值映射,允许相同的键对应多个值
std::multimap<std::string, int> fruits;
fruits.insert({"apple", 3});
fruits.insert({"apple", 5});
fruits.insert({"banana", 2});
// 输出每个键及其对应的值
for (const auto& fruit : fruits) {
std::cout << fruit.first << ": " << fruit.second << std::endl;
}
return 0;
}
性能考量
map
和 set
的性能主要取决于底层使用的数据结构。由于它们基于红黑树实现,因此大多数操作的时间复杂度为 O(log N)。这意味着随着容器中元素数量的增加,操作时间的增长速度较慢。
在需要频繁插入和删除操作时,map
和 set
仍然能保持良好的性能。然而,在需要频繁访问元素时,如果元素的分布很密集,那么使用哈希表(如 unordered_map
和 unordered_set
)可能会提供更好的性能。
总结
本文介绍了 C++ 中 map
和 set
的基本用法以及在实际编程中的应用案例。通过这些例子,我们看到了 map
和 set
在词频统计、去重和排序、动态排名以及图算法等方面的应用。此外,本文还讨论了自定义比较器和 multimap
的使用技巧,以及在性能方面的考量。