C++ 关联容器
什么是关联容器?
关联容器是C++ STL(标准模板库)中的一类重要容器,与顺序容器不同,关联容器中的元素是通过键(key)来组织和访问的,而不是通过位置。关联容器在内部通常使用红黑树等平衡树结构实现,这使得它们在查找、插入和删除操作上具有较高的效率(通常为对数时间复杂度)。
C++ STL提供了四种基本的关联容器:
set
:存储唯一键的集合,按照键排序map
:存储键-值对的集合,按照键排序,键唯一multiset
:允许重复键的集合,按照键排序multimap
:允许重复键的键-值对集合,按照键排序
C++11之后,还引入了基于哈希表实现的无序关联容器:unordered_set
、unordered_map
、unordered_multiset
和unordered_multimap
,它们提供了平均常数时间复杂度的操作。本文将重点介绍基于平衡树的关联容器。
set容器
set
是一个只包含唯一键的集合,它按照键的值自动排序。
基本操作
#include <iostream>
#include <set>
int main() {
// 创建一个set容器
std::set<int> numbers;
// 插入元素
numbers.insert(30);
numbers.insert(10);
numbers.insert(20);
numbers.insert(10); // 重复元素不会被插入
// 遍历输出set中的元素
std::cout << "Set elements: ";
for(const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 检查元素是否存在
if(numbers.find(10) != numbers.end()) {
std::cout << "Element 10 found in the set" << std::endl;
}
// 删除元素
numbers.erase(20);
// 再次遍历
std::cout << "After erasing 20: ";
for(const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 获取set的大小
std::cout << "Size of set: " << numbers.size() << std::endl;
return 0;
}
输出结果:
Set elements: 10 20 30
Element 10 found in the set
After erasing 20: 10 30
Size of set: 2
重要成员函数
insert(val)
:插入元素erase(val)
:删除指定值的元素find(val)
:查找元素,返回迭代器count(val)
:返回指定值元素的数量(对于set通常为0或1)clear()
:清空容器size()
:返回容器中元素的数量empty()
:检查容器是否为空
map容器
map
容器存储键值对(pair类型),并按照键自动排序。每个键在map中只能出现一次。
基本操作
#include <iostream>
#include <map>
#include <string>
int main() {
// 创建一个map容器,键类型为string,值类型为int
std::map<std::string, int> studentScores;
// 插入元素的几种方式
studentScores["Alice"] = 95;
studentScores.insert(std::make_pair("Bob", 89));
studentScores.insert({"Charlie", 78});
// 访问元素
std::cout << "Alice's score: " << studentScores["Alice"] << std::endl;
// 遍历map
std::cout << "Student scores:" << std::endl;
for(const auto& pair : studentScores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// 检查键是否存在
if(studentScores.find("David") == studentScores.end()) {
std::cout << "David is not in the map" << std::endl;
}
// 修改值
studentScores["Bob"] = 92;
// 删除元素
studentScores.erase("Charlie");
// 再次遍历
std::cout << "After modifications:" << std::endl;
for(const auto& pair : studentScores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
输出结果:
Alice's score: 95
Student scores:
Alice: 95
Bob: 89
Charlie: 78
David is not in the map
After modifications:
Alice: 95
Bob: 92
重要成员函数
operator[]
:访问或插入元素(如果键不存在)insert(pair)
:插入键值对erase(key)
:删除特定键的元素find(key)
:查找特定键,返回迭代器count(key)
:返回特定键的元素数量(对于map通常为0或1)clear()
:清空容器size()
:返回容器中元素的数量empty()
:检查容器是否为空
使用operator[]
访问不存在的键会创建一个新的键值对,值为默认构造的值。如果只想查询元素而不想插入,请使用find()
或count()
。
multiset容器
multiset
是set
的变体,允许存储重复的键。
基本操作
#include <iostream>
#include <set>
int main() {
// 创建一个multiset容器
std::multiset<int> numbers;
// 插入元素,包括重复元素
numbers.insert(30);
numbers.insert(10);
numbers.insert(20);
numbers.insert(10); // 可以插入重复元素
// 遍历输出
std::cout << "Multiset elements: ";
for(const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
// 计算特定值的元素数量
std::cout << "Count of 10: " << numbers.count(10) << std::endl;
// 删除所有特定值的元素
numbers.erase(10);
// 再次遍历
std::cout << "After erasing 10: ";
for(const auto& num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出结果:
Multiset elements: 10 10 20 30
Count of 10: 2
After erasing 10: 20 30
multimap容器
multimap
是map
的变体,允许一个键对应多个值。
基本操作
#include <iostream>
#include <map>
#include <string>
int main() {
// 创建一个multimap容器
std::multimap<std::string, std::string> dictionary;
// 插入元素,允许同一个键对应多个值
dictionary.insert({"apple", "a fruit"});
dictionary.insert({"apple", "a tech company"});
dictionary.insert({"book", "a collection of pages"});
// 遍历输出所有元素
std::cout << "Dictionary entries:" << std::endl;
for(const auto& entry : dictionary) {
std::cout << entry.first << ": " << entry.second << std::endl;
}
// 查找特定键的所有值
std::string key = "apple";
auto range = dictionary.equal_range(key);
std::cout << "\nAll definitions of '" << key << "':" << std::endl;
for(auto it = range.first; it != range.second; ++it) {
std::cout << " - " << it->second << std::endl;
}
return 0;
}
输出结果:
Dictionary entries:
apple: a fruit
apple: a tech company
book: a collection of pages
All definitions of 'apple':
- a fruit
- a tech company
关联容器的常见应用场景
-
字典和映射:存储键值对的查找表,如单词和定义、ID和用户信息等。
-
计数和统计:使用map或multimap统计元素出现次数。
-
去重:使用set去除重复元素。
-
排序集合:存储需要保持有序的元素集合。
-
符号表:在编译器或解释器中实现符号表。
实例:单词频率统计
#include <iostream>
#include <map>
#include <string>
#include <sstream>
int main() {
std::string text = "this is a sample text this is just an example text for word counting example";
std::map<std::string, int> wordFrequency;
// 将文本拆分为单词并计数
std::stringstream ss(text);
std::string word;
while(ss >> word) {
// 当单词不存在时,会创建一个计数为0的新条目,然后增加计数
wordFrequency[word]++;
}
// 输出每个单词的频率
std::cout << "Word frequencies:" << std::endl;
for(const auto& pair : wordFrequency) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
输出结果:
Word frequencies:
a: 1
an: 1
counting: 1
example: 2
for: 1
is: 2
just: 1
sample: 1
text: 2
this: 2
word: 1
自定义类型作为关联容器的键
要使用自定义类型作为关联容器的键,需要提供比较运算符或比较函数对象,以便容器能够正确排序和查找元素。
#include <iostream>
#include <set>
#include <string>
// 自定义类型
class Person {
public:
std::string name;
int age;
Person(const std::string& n, int a) : name(n), age(a) {}
// 定义 < 运算符,用于set的排序
bool operator<(const Person& other) const {
if(name != other.name)
return name < other.name;
return age < other.age;
}
};
int main() {
std::set<Person> people;
people.insert(Person("Alice", 25));
people.insert(Person("Bob", 30));
people.insert(Person("Alice", 30)); // 不同的人,因为年龄不同
std::cout << "People in the set:" << std::endl;
for(const auto& person : people) {
std::cout << person.name << ", " << person.age << std::endl;
}
return 0;
}
输出结果:
People in the set:
Alice, 25
Alice, 30
Bob, 30
关联容器的性能特点
关联容器使用平衡树(通常是红黑树)实现,提供以下性能特点:
- 查找操作:O(log n) 时间复杂度
- 插入操作:O(log n) 时间复杂度
- 删除操作:O(log n) 时间复杂度
- 元素始终有序:让按键遍历变得简单高效
如果不需要元素排序,但需要更快的查找、插入和删除操作,可以考虑使用C++11引入的无序关联容器(如unordered_map
和unordered_set
),它们基于哈希表实现,提供平均常数时间复杂度的操作。
总结
C++的关联容器是STL中非常强大的工具,它们提供了高效的键值存储和检索机制:
- set:存储唯一的已排序元素
- map:存储唯一键和对应值的已排序集合
- multiset:允许重复键的已排序集合
- multimap:允许一个键对应多个值的已排序集合
关联容器适用于需要快速查找、插入和删除操作的场景,尤其是当数据需要保持有序时。它们在许多实际应用中都非常有用,如字典、符号表、频率计数等。
练习题
- 创建一个程序,使用
map
存储学生姓名和成绩,然后按成绩从高到低排序输出。 - 使用
multimap
实现一个简单的电话簿,其中一个人可以有多个电话号码。 - 利用
set
统计一个文本文件中不重复单词的数量。 - 创建一个自定义类
Product
,包含产品名称和价格,使其能作为set
的键。