文章目录
树形关联式容器的四种类型
在C++标准模板库(STL)中,树形关联式容器主要包括 set、multiset、map 和 multimap 四种类型。这四种容器均基于红黑树(RB-tree)实现,确保了元素的有序性,并且提供了高效的查找、插入和删除操作。以下是每种容器的详细介绍和使用说明:
1. std::set
- 存储内容:set容器存储唯一键值,即每个元素都是唯一的,不能有重复。
- 键值关系:在set中,元素本身即被视为键(key),没有对应的值(value)部分。
- 排序:元素按照键的自然顺序或者通过自定义比较函数进行排序。
- 操作:支持多种操作,如查找(
find
、lower_bound
、upper_bound
、equal_range
)、插入(insert
)、删除(erase
)、遍历等。 - 示例:
std::set<int> s; s.insert(3); s.insert(1); s.insert(2); // set现在包含 [1, 2, 3]
2. std::multiset
- 存储内容:multiset类似于set,但它允许键值重复。
- 键值关系:与set一样,元素本身也是键,同样没有显式的值部分。
- 排序:同样按照键的自然顺序或者自定义比较函数排序。
- 操作:除了支持set的所有操作外,还允许插入多个具有相同键的元素。
- 示例:
std::multiset<int> ms; ms.insert(3); ms.insert(1); ms.insert(2); ms.insert(2); // 可以插入重复元素 // multiset现在包含 [1, 2, 2, 3]
3. std::map
- 存储内容:map容器存储键值对(key-value pairs),键是唯一的,值可以重复。
- 键值关系:每个元素都是一个
std::pair<const Key, T>
类型的对象,其中Key是键,T是值类型。 - 排序:元素按照键的升序排列。
- 操作:可以通过键访问对应的值,如查找(
find
)、插入(insert
)、修改值(operator[]
)、删除(erase
)等。 - 示例:
std::map<std::string, int> m; m["apple"] = 1; m["banana"] = 2; // map现在包含 {"apple": 1, "banana": 2}
4. std::multimap
- 存储内容:multimap类似于map,但允许键值对中有重复的键。
- 键值关系:每个元素同样是键值对形式,但同一个键可以对应多个不同的值。
- 排序:键依然有序,对于同一个键,其对应的多个值的顺序不确定。
- 操作:支持map的所有操作,并增加了针对相同键值对的插入和遍历功能。
- 示例:
std::multimap<std::string, int> mm; mm.insert({"apple", 1}); mm.insert({"banana", 2}); mm.insert({"apple", 3}); // 同一键可对应多个值 // multimap现在包含 {"apple": [1, 3], "banana": [2]}
以上四种容器都支持迭代器遍历,常用于需要高效查找和有序存储场景,尤其是当元素的排序非常重要时。同时,由于底层实现为平衡二叉搜索树,因此平均时间复杂度接近O(log n)。
继续深入了解C++树形关联容器的特性与用法
共享特性
-
迭代器稳定性:在set、multiset、map和multimap中,除了
erase
操作中的迭代器以及可能导致容器重新调整大小的操作(如insert
时可能触发容器扩容)外,其他操作不会使已存在的迭代器失效。 -
内存分配:这些容器会动态地管理内存,自动调整容量以适应元素数量的增长。
-
效率:由于底层采用了红黑树,插入、删除和查找操作的时间复杂度通常是O(log n),n为容器中元素的数量。
-
定制化:可以通过提供自定义的比较函数对象(Comparator)来改变元素的排序规则。此外,也可以通过指定分配器(Allocator)来自定义内存管理方式。
特殊用法及注意事项
-
插入操作:
insert
方法不仅可以插入单个元素,还可以插入一个范围内的元素或一个由<initializer_list>
初始化的元素序列。- 对于map和multimap,插入时如果不指定值,将默认构造一个值。
-
查找操作:
find
方法返回一个指向所查找键的迭代器,如果未找到,则返回尾后迭代器。lower_bound
和upper_bound
分别返回第一个大于等于(包括等于)给定键的元素迭代器和第一个大于给定键的元素迭代器,这对于区间查询非常有用。
-
删除操作:
erase
方法可以接受一个键、一个迭代器或一个迭代器范围来删除元素。对于map和multimap,删除的是键值对;而对于set和multiset,删除的就是元素本身。
-
遍历:
- 除了普通的前向迭代遍历,还可以通过
rbegin()
和rend()
进行反向迭代遍历。
- 除了普通的前向迭代遍历,还可以通过
总结来说,C++中的set、multiset、map和multimap都是非常强大的容器,它们为程序员提供了方便的手段来管理和操作有序数据集合,尤其适合那些需要快速查找、插入和删除元素的场景。在实际编程过程中,应根据数据的特点(是否允许重复、关注键还是键值对)选择合适的容器类型。
高级特性和应用场景
-
集合运算:set和multiset类容器支持集合间的数学运算,如并集(
set_union
)、交集(set_intersection
)、差集(set_difference
)和对称差集(set_symmetric_difference
)。这些运算可以非常方便地用于处理集合型数据问题。 -
键值对操作:对于map和multimap,可以直接通过键来获取或修改对应的值。例如,
map::operator[]
可以用来查找或插入键值对,如果键不存在,则会插入一个新的键值对并返回引用到新插入的值。
std::map<std::string, int> m;
m["apple"] = 1; // 插入键值对
int apple_count = m["apple"]; // 获取值,如果"apple"不存在则插入并初始化为0
-
范围查询:通过
lower_bound
和upper_bound
可以轻松找出所有满足一定条件(比如年龄在某一范围内)的人群记录,这对于数据分析和统计十分有用。 -
高效迭代:由于树形结构的特性,即使在大数据量下,通过迭代器遍历容器也能保持相对较高的性能。
-
空间效率:虽然红黑树保证了log(n)级别的操作效率,但相比vector等线性容器,树形容器的空间利用率较低,尤其是在元素数量较少时。因此,在权衡空间和时间效率时,需要考虑具体的应用场景。
-
并发安全:C++ STL中的这些容器都不是线程安全的,如果需要在多线程环境中使用,需自行添加同步机制或考虑使用C++17引入的
std::unordered_map/std::unordered_set
配合std::mutex
等工具,或者使用第三方库提供的线程安全版本容器。
总之,熟练掌握C++ STL中的树形关联容器,不仅能提升代码的简洁度和执行效率,还能帮助开发者更好地应对各种数据组织和处理的需求。
实际使用例子:
当然,以下是一些关于C++中set、multiset、map和multimap的实际使用例子:
1. std::set的例子
#include <iostream>
#include <set>
int main() {
std::set<int> numbers;
// 插入元素
numbers.insert(5);
numbers.insert(3);
numbers.insert(1);
numbers.insert(4);
numbers.insert(2);
// 打印集合中的所有元素(自动排序)
for (const auto& num : numbers) {
std::cout << num << ' ';
}
// 输出:1 2 3 4 5
// 查找元素
if (numbers.find(3) != numbers.end()) {
std::cout << "3 is in the set.\n";
}
// 删除元素
numbers.erase(3);
return 0;
}
2. std::multiset的例子
#include <iostream>
#include <multiset>
int main() {
std::multiset<int> multipleNumbers;
// 插入重复元素
multipleNumbers.insert(3);
multipleNumbers.insert(1);
multipleNumbers.insert(2);
multipleNumbers.insert(2);
multipleNumbers.insert(3);
// 打印集合中的所有元素(自动排序)
for (const auto& num : multipleNumbers) {
std::cout << num << ' ';
}
// 输出:1 2 2 3 3
// 计算特定元素的数量
auto count = std::count(multipleNumbers.begin(), multipleNumbers.end(), 2);
std::cout << "\nNumber of 2s: " << count << '\n';
return 0;
}
3. std::map的例子
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> studentGrades;
// 插入键值对
studentGrades["Alice"] = 85;
studentGrades["Bob"] = 90;
studentGrades["Charlie"] = 88;
// 访问并修改值
studentGrades["Alice"] += 5;
// 打印所有学生的成绩
for (const auto& pair : studentGrades) {
std::cout << pair.first << ": " << pair.second << '\n';
}
// 输出:Alice: 90 Bob: 90 Charlie: 88
// 查找学生并打印成绩
if (auto it = studentGrades.find("Bob"); it != studentGrades.end()) {
std::cout << "Bob's grade: " << it->second << '\n';
}
return 0;
}
4. std::multimap的例子
#include <iostream>
#include <multimap>
int main() {
std::multimap<std::string, int> studentCourses;
// 插入多个相同的键值对
studentCourses.insert({"Alice", 101});
studentCourses.insert({"Bob", 101});
studentCourses.insert({"Alice", 102});
studentCourses.insert({"Charlie", 101});
// 打印Alice参加的所有课程
for (const auto& pair : studentCourses.equal_range("Alice")) {
std::cout << pair.first << ": " << pair.second << '\n';
}
// 输出:Alice: 101 Alice: 102
return 0;
}
python推荐学习汇总连接:
50个开发必备的Python经典脚本(1-10)
50个开发必备的Python经典脚本(41-50)
————————————————
最后我们放松一下眼睛