Unordered containers(无序容器)详解

基础介绍

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值