C++ STL无序关联式容器 (一) unordered_map / unordered_set / unordered_multi-

无序容器是 C++ 11 标准才正式引入到 STL 标准库中的,如果要使用该类容器则必须选择支持 C++ 11 标准的编译器。和关联式容器一样无序容器也使用键值对(pair 类型)的方式存储数据。不过,它们有本质上的不同:
关联式容器的底层实现采用的树存储结构,更确切的说是红黑树结构;
无序容器的底层实现采用的是哈希表的存储结构。

因此和关联式容器相比,无序容器具有以下 2 个特点:
无序容器内部存储的键值对是无序的,各键值对的存储位置取决于该键值对中的键
和关联式容器相比,无序容器擅长通过指定键查找对应的值(平均时间复杂度为 O(1))但对于使用迭代器遍历容器中存储的元素,无序容器的执行效率则不如关联式容器
无需容器底层实现原理
在这里插入图片描述

一、unordered_map

(1)基本应用及原理 (声明 建立 插入 查找 迭代器访问举例)

unordered_map 容器,就是"无序 map 容器",它不会像 map 容器那样对存储的数据进行排序。底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序
换句话说,unordered_map 容器和 map 容器仅有一点不同,即 map 容器中存储的数据是有序的,而 unordered_map 容器中是无序的。
头文件/<unordered_map>

template < class Key,                        //键值对中键的类型
           class T,                          //键值对中值的类型
           class Hash = hash<Key>,           //容器内部存储键值对所用的哈希函数
           class Pred = equal_to<Key>,       //判断各个键值对键相同的规则
           class Alloc = allocator< pair<const Key,T> >  // 指定分配器对象的类型
           > class unordered_map;
以上 5 个参数中,必须显式给前 2 个参数传值,并且除特殊情况外,最多只需要使用前 4 个参数
默认哈希函数只适用于基本数据类型(包括 string 类型),而不适用于自定义的结构体或者类。
当无序容器中存储键值对的键为自定义类型时,默认的哈希函数 hash 以及比较函数 equal_to 将不再适用
只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数。

(2) 元素访问的三种方法(迭代器 / 键 / at)

//求两个链表的相交节点
ListNode *p=headA,*q=headB,*res=NULL; 
unordered_map<ListNode*,int> Hash;  //声明 键值对<key,value>
for(int i=0;p;p=p->next,i++){
     Hash.insert<make_pair(p,i)>;   //三种插入方式都可以
     Hash.insert({p,i});            //也可添加多个键值对多个,之间用逗号
     Hash[p]=i;
} 

迭代器访问元素
unordered_map<ListNode*,int>::iterator it; 
while(q){
     //通过key去找 返回迭代指针it 再it去访问value
     it=Hash.find(q);    //find()函数
     if(it!=Hash.end()){ //找到了
         //res=q//两个都可以本题,但是迭代器更通用
         res=it->first;
         break;
     }
     q=q->next;
}
return res;
键访问元素
实现了对 [ ] 运算符的重载,像“利用下标访问普通数组中元素”那样,
通过目标键值对的键获取到该键对应的值。
    //创建 umap 容器
    unordered_map<string, string> umap{
        {"Python教程","http://c.biancheng.net/python/"},
        {"Java教程","http://c.biancheng.net/java/"},
        {"Linux教程","http://c.biancheng.net/linux/"} };
    //获取 "Java教程" 对应的值
    string str = umap["Java教程"];
    cout << str << endl;
    
如果当前容器中并没有存储以 [ ] 运算符内指定的元素作为键的键值对,
则此时 [ ] 运算符的功能将转变为:向当前容器中添加以目标元素为键的键值对
     unordered_map<string, string> umap;
    //[] 运算符在 = 右侧
    string str = umap["STL教程"];
    //[] 运算符在 = 左侧
    umap["C教程"] = "http://c.biancheng.net/c/";[ ] 运算符位于赋值号(=)右侧时,则新添加键值对的键为 [ ] 运算符内的元素,其值为键值对要求的值类型的默认值(string 类型默认值为空字符串);
当 [ ] 运算符位于赋值号(=)左侧时,则新添加键值对的键为 [ ] 运算符内的元素,其值为赋值号右侧的元素。
和使用 [ ] 运算符一样,at() 成员方法需要根据指定的键才能从容器中找到该键对应的值;
不同之处是,若在当前容器中查找失败该方法不会向容器中添加新的键值对而是直接抛出out_of_range异常。
    //创建 umap 容器
    unordered_map<string, string> umap{
        {"Python教程","http://c.biancheng.net/python/"},
        {"Java教程","http://c.biancheng.net/java/"},
        {"Linux教程","http://c.biancheng.net/linux/"} };
    //获取指定键对应的值
    string str = umap.at("Python教程");
    cout << str << endl;
    //执行此语句会抛出 out_of_range 异常
    //cout << umap.at("GO教程");

(3)unordered_map迭代器遍历

  unordered_map<char,vector<int>> Hash;    
  unordered_map<char,vector<int>>::iterator it;     
    
  for(it=Hash.begin();it!=Hash.end();it++) { //hash遍历
         for(int i=0;i<(it->second).size();i++){
             cout<<it->first<<(it->second)[i];
         }
  }

(4) 哈希表元素的插入insert() & emplace()

insert()四种插入方法,与insert()相比emplace()更高效

方法一:pair<iterator, bool> emplace ( Args&&… args );

参数 args 表示可直接向该方法传递创建新键值对所需要的 2 个元素的值,其中第一个元素将作为键值对的键,另一个作为键值对的值。也就是说,该方法无需我们手动创建键值对,其内部会自行完成此工作。
成功添加新键值对时,返回的迭代器指向新添加的键值对,bool 值为 True;
添加新键值对失败时,说明容器中本就包含一个键相等的键值对,此时返回的迭代器指向的就是容器中键相同的这个键值对,bool 值为 False。

    unordered_map<string, string> umap;
    //定义接受 emplace() 方法的 pair 类型变量    
	pair<unordered_map<string, string>::iterator, bool> ret;
	
    //调用 emplace() 方法
    ret = umap.emplace("STL教程", "http://c.biancheng.net/stl/");
    //输出 ret 中包含的 2 个元素的值
    cout << "bool =" << ret.second << endl;
    cout << "iter ->" << ret.first->first << " " << ret.first->second << endl;

方法二:iterator emplace_hint ( const_iterator position, Args&&… args );

与方法一相同其内部会自行构造新键值对,只需向其传递构建该键值对所需的 2 个元素
返回值仅是一个迭代器,而不再是 pair 类型变量。
成功添加时,返回的迭代器指向新添加的键值对
添加失败时,返回的迭代器指向的是容器中和要添加键值对键相同的那个键值对
还需要传递一个迭代器作为第一个参数,该迭代器表明将新键值对添加到容器中的位置。需要注意的是,新键值对添加到容器中的位置,并不是此迭代器说了算,最终仍取决于该键值对的键的值
方法中传入的迭代器,仅是给 unordered_map 容器提供一个建议,并不一定会被容器采纳。

{
    //创建 umap 容器
    unordered_map<string, string> umap;
    //定义一个接受 emplace_hint() 方法的迭代器
    unordered_map<string,string>::iterator iter;
    //调用 empalce_hint() 方法
    iter = umap.emplace_hint(umap.begin(),"STL教程", "http://c.biancheng.net/stl/");
    //输出 emplace_hint() 返回迭代器 iter 指向的键值对的内容
    cout << "iter ->" << iter->first << " " << iter->second << endl;
}

(5)哈希表元素的删除erase() & clear()

(http://c.biancheng.net/view/7247.html)

方法一:迭代器删除 iterator erase ( const_iterator position );
position 为指向容器中某个键值对的迭代器,该方法会返回一个指向被删除键值对之后位置的迭代器

方法二:关键字删除 size_type erase ( const key_type& k );
将要删除键值对的键作为参数直接传给 erase() 方法,k 表示目标键值对的键的值;该方法会返回一个整数,其表示成功删除的键值对的数量

方法三:删除指定范围内所有键值对 iterator erase ( const_iterator first, const_iterator last );
其中 first 和 last 都是正向迭代器,[first, last)半闭半开范围内的所有键值对都会被 erase() 方法删除;同时,该方法会返回一个指向被删除的最后一个键值对之后一个位置的迭代器。

(6)队列queue以及pair的运用例题

unordered_map更详细的讲解传送门http://c.biancheng.net/view/7231.html

count(key) 在容器中查找以 key 键的键值对的个数
empty() 若容器为空,则返回 true;否则 false

二、unordered_set

unordered_set 容器,即“无序 set 容器”,即 unordered_set 容器和 set 容器唯一的区别就在于 set 容器会自行对存储的数据进行排序,而 unordered_set 容器不会。
具有以下几个特性:
不再以键值对的形式存储数据,而是直接存储数据的值;
容器内部存储的各个元素的值都互不相等,且不能被修改。
不会对内部存储的数据进行排序

对于 unordered_set 容器不以键值对的形式存储数据,也可以这样认为,即 unordered_set 存储的都是键和值相等的键值对,为了节省存储空间,该类容器在实际存储时选择只存储每个键值对的值。

<unordered_set>头文件
unordered_set 容器的类模板定义如下

template < class Key,            //容器中存储元素的类型
           class Hash = hash<Key>,    //确定元素存储位置所用的哈希函数
           class Pred = equal_to<Key>,   //判断各个元素是否相等所用的函数
           class Alloc = allocator<Key>   //指定分配器对象的类型
           > class unordered_set;
以上 4 个参数中,只有第一个参数没有默认值,这意味着如果我们想创建一个 unordered_set 容器
至少需要手动传递 1 个参数。
事实上,在 99% 的实际场景中最多只需要使用前 3 个参数(各自含义如表 1 所示),
最后一个参数保持默认值即可
默认哈希函数 hash <Key> 只适用于基本数据类型(包括 string 类型)不适用于自定义的结构体或者类。
自定义的只能自己设计适用该类型的哈希函数和比较函数,并显式传递给 Hash 参数和 Pred 参数

创建和使用 unordered_map 和 unordered_multimap 容器的所有方式完全适用于 unordereded_set 容器
还可以调用 unordered_set 模板中提供的拷贝构造函数,将现有 unordered_set 容器中存储的元素全部用于为新建 unordered_set 容器初始化。

//创建并初始化
std::unordered_set<std::string> uset{ "http://c.biancheng.net/c/",
                                      "http://c.biancheng.net/java/",
                                      "http://c.biancheng.net/linux/" };
//拷贝构造 必须保证 2个容器的类型完全相同。
std::unordered_set<std::string> uset2(uset);

//如果不想全部拷贝可以使用 unordered_set 类模板提供的迭代器 
//如uset2 容器,其内部就包含 uset 容器中除第 1 个元素外的所有其它元素。
std::unordered_set<std::string> uset2(++uset.begin(),uset.end());

unordered_set各种使用方法参见set使用方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值