一、map
1.1 map原理介绍
-
基于红黑树(平衡二叉搜索树),红黑树是自平衡二叉搜索树,从根到叶子的最长路径不超过最短路径的2倍,插入删除后通过旋转和重新着色保持平衡。
// 红黑树节点结构
struct RBTreeNode {
Key key;
Value value;
Color color; // RED or BLACK
RBTreeNode* left;
RBTreeNode* right;
RBTreeNode* parent;
};
- 比较函数(默认为
std::less<Key>,即升序排列,std::greater<Key>,即降序排列,lambda表达式)
#include <map>
#include <functional> // 需要包含这个头文件
#include <iostream>
#include <string>
using namespace std;
int main(){
//默认比较器(less),按照键进行升序排序
map<int,string> defaut_map;
defaut_map[1] = "one";
defaut_map[3] = "three";
defaut_map[2] = "two";
cout<<"****默认比较器****"<<endl;
for (const auto& p : defaut_map) {
cout << p.first << ":" << p.second << " ";
// 输出: 3:three 2:two 1:one
}
cout<<endl;
//less比较器,按照键进行升序排序
map<int,string,less<int>> increasing_map;
increasing_map[1] = "one";
increasing_map[3] = "three";
increasing_map[2] = "two";
cout<<"****less比较器****"<<endl;
for (const auto& p : increasing_map) {
cout << p.first << ":" << p.second << " ";
// 输出: 3:three 2:two 1:one
}
cout<<endl;
//greater比较器,按照键进行升序排序
map<int, string, greater<int>> descending_map;
descending_map[1] = "one";
descending_map[3] = "three";
descending_map[2] = "two";
cout<<"****greater比较器****"<<endl;
for (const auto& p : descending_map) {
cout << p.first << ":" << p.second << " ";
// 输出: 3:three 2:two 1:one
}
cout<<endl;
// 定义lambda比较器 - 按字符串长度排序
auto valueLengthComparator = [](const string& a, const string& b) {
return a.length() < b.length();
};
// 使用decltype获取lambda类型,并传递lambda给构造函数
map<string, int, decltype(valueLengthComparator)> lengthMap(valueLengthComparator);
lengthMap["apple"] = 1;
lengthMap["banana"] = 2;
lengthMap["cherry"] = 3;
lengthMap["date"] = 4;
cout << "****按字符串长度排序****:" << endl;
for (const auto& p : lengthMap) {
cout << p.first << " (长度:" << p.first.length() << "): " << p.second<<" " ;
}
cout<<endl;
}
- 键的唯一性。提问,在lengthMap的打印过程中会发现,cherry并没有被打印出来,请说明原因? 原因:map中的键具有唯一性,而唯一性是根据比较器来判断的,在lengthMap中比较器为比较字符长度,因此当cherry和banana进行比较时,由于长度一致,所以map将其视为与banana一致,并用cherry的键值3覆盖了banana原本的键值2。
1.2 map的增删改查
先给出总结:
-
插入使用
emplace()或insert() -
查找使用
find()或contains()(C++20) -
修改前先检查键是否存在
-
注意
operator[]的创建副作用 -
删除后不要使用失效的迭代器
| 操作 | 方法 | 返回值 | 说明 |
|---|---|---|---|
| 增 | insert(), emplace(), operator[] | 插入结果或引用 | operator[] 最方便但可能意外创建元素 |
| 删 | erase(key), erase(iterator) | 删除数量 | 可按键、迭代器或范围删除 |
| 改 | operator[], at(), 迭代器 | 无或引用 | 只能修改值,不能修改键 |
| 查 | find(), count(), contains() | 迭代器或布尔值 | find() 最常用,contains() 最直观(C++20) |
1.2.1 map添加元素
#include <map>
#include <functional> // 需要包含这个头文件
#include <iostream>
#include <string>
using namespace std;
int main(){
//增加元素
cout<<"**** insert ****"<<endl;
map<int, string> myMap_insert;
// 方法1.1:insert + make_pair
myMap_insert.insert(make_pair(1, "one"));
// 方法1.2:insert + pair构造函数
myMap_insert.insert(pair<int, string>(2, "two"));
// 方法1.3:insert + 花括号(C++11)
myMap_insert.insert({3, "three"});
// 方法1.4:insert + 迭代器提示(性能优化)
auto insert_hint = myMap_insert.end();
myMap_insert.insert(insert_hint, {4, "four"});
// 检查插入结果
auto result = myMap_insert.insert({1, "ONE"}); // 键1已存在,不会插入
cout << "插入结果: " << (result.second ? "成功" : "失败") << endl;
for(const auto &it:myMap_insert){
cout<<it.first<<":"<<it.second<<", ";
}
cout<<endl;
map<int, string> myMap_emplace;
// emplace直接构造元素,避免临时对象
cout<<"**** emplace ****"<<endl;
myMap_emplace.emplace(1, "one"); // 最常用
myMap_emplace.emplace(2, "two");
myMap_emplace.emplace(3, "three");
// emplace_hint带提示位置
auto emplace_hint = myMap_emplace.find(2);
if (emplace_hint != myMap_emplace.end()) {
myMap_emplace.emplace_hint(emplace_hint, 4, "four");
}
// 显示结果
for (const auto& [key, value] : myMap_emplace) {
cout << key << ":" << value <<", ";
}
cout<<endl;
//使用 operator[]
cout<<"**** operator[] ****"<<endl;
map<int, string> myMap;
// 如果键不存在,会创建并插入默认值
myMap[1] = "one";
myMap[2] = "two";
myMap[3] = "three";
// 注意:operator[] 会创建元素,即使只是读取
cout <<"myMap[4]: " <<myMap[4] << endl; // 输出空字符串,但创建了键4
// 显示结果
for (const auto& [key, value] : myMap) {
cout << key << ":" << value <<", ";
}
cout<<endl;
return 0;
}
1.2.2 map删除元素
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<int, string> myMap = {
{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"}
};
cout << "初始map: ";
for (const auto& p : myMap) cout << p.first << " ";
cout << endl;
// 方法2.1:通过键删除
size_t count = myMap.erase(2); // 返回删除的元素数量(0或1)
cout << "删除键2,结果: " << count << endl;
// 方法2.2:通过迭代器删除
auto it = myMap.find(3);
if (it != myMap.end()) {
myMap.erase(it); // 删除找到的元素
cout << "通过迭代器删除键3" << endl;
}
// 方法2.3:删除一个范围 [first, last)
auto first = myMap.find(4);
auto last = myMap.end();
if (first != myMap.end()) {
myMap.erase(first, last); // 删除从4到末尾的所有元素
cout << "删除范围 [4, end)" << endl;
}
cout << "删除后map: ";
for (const auto& p : myMap) cout << p.first << " ";
cout << endl;
return 0;
}
1.2.3 map修改元素
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<int, string> myMap = {
{1, "one"}, {2, "two"}, {3, "three"}
};
cout << "修改前: ";
for (const auto& [k, v] : myMap) cout << k << ":" << v << " ";
cout << endl;
// 方法3.1:使用 operator[](键必须存在)
myMap[1] = "ONE"; // 直接修改
myMap[2] = "TWO";
// 方法3.2:使用迭代器
auto it = myMap.find(3);
if (it != myMap.end()) {
it->second = "THREE"; // 修改值
}
// 方法3.3:使用 at()(C++11,会检查边界)
try {
myMap.at(1) = "ONE_MODIFIED";
// myMap.at(10) = "ten"; // 会抛出std::out_of_range异常
} catch (const out_of_range& e) {
cout << "异常: " << e.what() << endl;
}
cout << "修改后: ";
for (const auto& [k, v] : myMap) cout << k << ":" << v << " ";
cout << endl;
return 0;
}
1.2.4 map查询元素
#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
map<int, string> myMap = {
{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}
};
// 方法4.1:使用 find() - 最常用
auto it = myMap.find(2);
if (it != myMap.end()) {
cout << "找到键2: " << it->second << endl;
} else {
cout << "未找到键2" << endl;
}
// 方法4.2:使用 count() - 检查存在性
if (myMap.count(3) > 0) {
cout << "键3存在" << endl;
} else {
cout << "键3不存在" << endl;
}
// 方法4.3:使用 contains() - C++20
#if __cplusplus >= 202002L
if (myMap.contains(4)) {
cout << "键4存在" << endl;
}
#endif
// 方法4.4:使用 equal_range() - 查找键范围(对multimap更有用)
auto range = myMap.equal_range(2);
for (auto it = range.first; it != range.second; ++it) {
cout << "equal_range找到: " << it->first << ":" << it->second << endl;
}
return 0;
}
1.2.5 时间复杂度总览
| 操作 | 平均情况 | 最坏情况 | 说明 |
|---|---|---|---|
| 插入 | O(log n) | O(log n) | 红黑树保持平衡 |
| 删除 | O(log n) | O(log n) | 红黑树保持平衡 |
| 查找 | O(log n) | O(log n) | 二分查找特性 |
| 修改 | O(log n) | O(log n) | 需要先查找 |
| 遍历 | O(n) | O(n) | 访问所有元素 |
二、unordered_map
2.1 unordered_map原理介绍
unordered_map 与 map 最大的不同在于,unordered_map 内部的元素不是排序的,而是通过哈希函数组织到桶(bucket)中,因此其元素的存储顺序是混乱的。unordered_map包含以下特性:
-
无序性:元素在容器中的顺序并不是按照键的某种顺序(如升序或降序)排列的,而是由哈希函数和键的值决定。遍历
unordered_map时,你无法得到一个有特定顺序的结果。 -
唯一键:每个键在
unordered_map中只能出现一次。 -
高效的查找性能:基于哈希表实现,其平均时间复杂度为 O(1),即在平均情况下,查找、插入和删除操作都非常快。
-
内存使用:为了维持高效的查找性能,它可能会消耗比
map更多的内存。
2.2 unordered_map的增删改查
2.2.1 unordered_map添加元素
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
std::unordered_map<std::string, int> u_map;
// 方法1: 使用 insert 成员函数
auto result1 = u_map.insert({"key1", 100});
// result1 是一个 pair<iterator, bool>,第二个元素表示插入是否成功
// 方法2: 使用 operator[]
u_map["key2"] = 200; // 如果 key 不存在则插入,存在则修改
// 方法3: 使用 emplace (C++11)
auto result2 = u_map.emplace("key3", 300);
// 方法4: 使用 insert 和 make_pair
u_map.insert(std::make_pair("key4", 400));
// 批量插入
std::unordered_map<std::string, int> additional = {{"key5", 500}, {"key6", 600}};
u_map.insert(additional.begin(), additional.end());
// 显示结果
for (const auto& [key, value] : u_map) {
cout << key << ":" << value <<", ";
}
cout<<endl;
}
2.2.2 unordered_map删除元素
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
// 批量插入
std::unordered_map<std::string, int> u_map = {{"key1", 100}, {"key2", 200},{"key3", 300}, {"key4",400},{"key5", 500}, {"key6", 600}};
// 方法1: 通过 key 删除
size_t count = u_map.erase("key1"); // 返回删除的元素数量(0或1)
// 方法2: 通过迭代器删除
auto it = u_map.find("key2");
if (it != u_map.end()) {
u_map.erase(it);
}
// 方法3: 删除范围内的元素
auto begin = u_map.begin();
auto end = u_map.find("key4");
if (end != u_map.end()) {
u_map.erase(begin, end); // 删除 [begin, end) 范围内的元素
}
// 显示结果
for (const auto& [key, value] : u_map) {
cout << key << ":" << value <<", ";
}
cout<<endl;
// 方法4: 清空所有元素
u_map.clear();
// 显示结果
for (const auto& [key, value] : u_map) {
cout << key << ":" << value <<", ";
}
cout<<endl;
}
2.2.3 unordered_map修改元素
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
// 批量插入
std::unordered_map<std::string, int> u_map = {{"key1", 100}, {"key2", 200},{"key3", 300}, {"key4",400},{"key5", 500}, {"key6", 600}};
// 方法1: 使用 operator[] (如果key不存在会插入新元素)
u_map["existing_key"] = 999;
// 方法2: 使用 at() (如果key不存在会抛出异常)
try {
u_map.at("existing_key") = 888;
} catch (const std::out_of_range& e) {
std::cout << "Key not found: " << e.what() << std::endl;
}
// 方法3: 先查找再修改
auto it = u_map.find("existing_key");
if (it != u_map.end()) {
it->second = 777; // 修改值
}
// 显示结果
for (const auto& [key, value] : u_map) {
cout << key << ":" << value <<", ";
}
cout<<endl;
}
2.2.4 unordered_map查找元素
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
// 批量插入
std::unordered_map<std::string, int> u_map = {{"key1", 100}, {"key2", 200},{"key3", 300}, {"key4",400},{"key5", 500}, {"key6", 600}};
// 方法1: 使用 at() (安全,不存在时抛出异常)
try {
int value2 = u_map.at("key");
} catch (const std::out_of_range& e) {
// 处理key不存在的情况
cout<<"The key does not exist."<<endl;
}
// 方法2: 使用 find()
auto it = u_map.find("key2");
if (it != u_map.end()) {
int value3 = it->second;
std::cout << "Found: " << value3 << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
// 方法4: 使用 count() 检查是否存在
if (u_map.count("key3") > 0) {
std::cout << "Key exists" << std::endl;
}
// 显示结果
for (const auto& [key, value] : u_map) {
cout << key << ":" << value <<", ";
}
cout<<endl;
}
2.3 unordered_map与map的核心差异对比
| 特性 | std::unordered_map | std::map |
|---|---|---|
| 底层数据结构 | 哈希表 | 红黑树(平衡二叉搜索树) |
| 元素顺序 | 无序 | 按键的升序排列 |
| 时间复杂度 | 平均 O(1),最坏 O(n) | 稳定 O(log n) |
| 内存占用 | 较高(哈希桶) | 较低(树节点) |
| 迭代器稳定性 | 插入可能使所有迭代器失效 | 迭代器基本稳定 |
三、set
与map类似,但存储内容为键而非键值对。
四、unordered_set
与unordered_map类似,但存储内容为键而非键值对。

1224

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



