基础介绍
Unordered container(无序容器)于c++11引入。无序容器基于哈希表实现;有序容器(std::map/std::set)基于红黑树实现。
依赖的头文件:#include <unordered_map>
#include <unordered_set>
无序容器的种类
std::unordered_set<key>
std::unordered_multiset<key>
std::unordered_map<key, value>
std::unordered_multimap<key, value>
引入无序容器的原因
提到引入无序容器的原因就要涉及有序容器,要对比们的优缺点来说:
- 有序容器查找、插入、删除操作慢,而无序容器查找、插入、删除速度快
时间复杂度分析
std::unordered_map<string, int> map;
// 1. 插入操作 - O(1) 平均情况
map["key"] = 1; // 平均 O(1),最坏 O(n),理解了哈希表的原理就能理解为什么平均是是O(1),为什么最坏的情况是O(n)// 2. 查找操作 - O(1) 平均情况
auto it = map.find("key"); // 平均 O(1),最坏 O(n)// 3. 删除操作 - O(1) 平均情况
map.erase("key"); // 平均 O(1),最坏 O(n)
无序容器的基础使用方法
std::unordered_map<std::string, int> umap;
// 插入元素
umap["key"] = 1;
umap.insert({"key2", 2});
umap.emplace("key3", 3);
// 查找元素
auto it = umap.find("key");
if (it != umap.end()) {
std::cout << it->second << std::endl;
}
// 删除元素
umap.erase("key");
// 遍历
for (const auto& [key, value] : umap) {
std::cout << key << ": " << value << std::endl;
}
无序容器使用注意事项
- 迭代器失效
std::unordered_map<std::string, int> map;
//错误示范
for(auto it = map.begin(); it != map.end; it++)
{
if(conditions)
{
map.erase(it); //此时it已失效,再it++可能会发生错误
}
}
//正确的例子
for(auto it = map.begin(); it != map.end)
{
if(conditions)
{
it = map.erase(it); //此时it已失效,再it++可能会发生错误
}
else
{
it++;
}
}
- 重哈希导致的引用失效
std::unordered_map<std::string, std::vector<int>> map;
auto &vec = map["key"];
map.insert({"newkey",{1,2,3}}); //危险可能会导致重哈希
vec.push_back(4); //如果发生重哈希,那么此行为是未定义的
//正确的使用实例
auto it = map.find("key");
if(it != map.end)
{
it.second.push_back(4);
}
- 键值修改
std::unordered_set<string> set;
set.insert("hello");
// 错误:不能直接修改set中的元素
for (auto& str : set) {
str[0] = 'H'; // 未定义行为!
}
// 正确方式:删除旧值,插入新值
auto it = set.find("hello");
if (it != set.end()) {
string new_value = *it;
new_value[0] = 'H';
set.erase(it);
set.insert(new_value);
}
- emplace与insert方法的区别
通过下面的实例可以看到emplace在涉及负载类型时相对于insert方法会减少拷贝或移动的开销。
class ExpensiveToCopy {
string large_string;
vector<int> large_vector;
public:
ExpensiveToCopy(string s, vector<int> v)
: large_string(std::move(s)), large_vector(std::move(v)) {
cout << "Constructor" << endl;
}
ExpensiveToCopy(const ExpensiveToCopy&) {
cout << "Expensive Copy!" << endl;
}
};
std::map<int, ExpensiveToCopy> m;
// insert方式:产生临时对象
m.insert({1, ExpensiveToCopy("large string", {1,2,3,4,5})});
// 构造顺序:
// 1. 构造ExpensiveToCopy临时对象
// 2. 构造pair临时对象
// 3. 移动或拷贝到map中
// emplace方式:直接在目标位置构造
m.emplace(1, "large string", vector<int>{1,2,3,4,5});
// 直接在map的内存位置构造对象,没有临时对象
- std::unordered_multimap与std::unordered_map的区别
unorfered_multimap支持相同的key支持插入,而unordered_map则不支持相同的key插入,仅保持最后一个key值的数值,请看下面的例子:
std::unordered_multimap<std::string, int> phonebook;
//电话簿,一个姓名可以拥有多个电话
phonebook.insert({"jack", 122225552});
phonebook.insert({"jack", 122225553});
phonebook.insert({"jack", 122225554});
std::cout<<"map size = "<<phonebook.size()<<std::endl; //此处结果返回为3
//std::unordered_map<std::string, int> map;
map.insert({"key", 1});
map.insert({"key", 2}); //覆盖数值1
std::cout<<"map size = "<<map.size()<<std::endl; //此处结果返回为1