C++--unordered_set和unordered_map的使用
unordered_set和unordered_map
- unordered_set和unordered_map的使用
- 1. unordered系列关联式容器
- 2. unorder_set
- 2.1 unorder_set的介绍
- 2.2 unorder_set的使用
- 2.2.1 unordered_set的定义方式
- 2.2.2 unorder_set接口的使用
- 2.2.3 使用示例
- 3. unordered_multiset
- 3.1 使用示例
- 4. unordered_set和set的使用差异
- 5. unorder_map
- 5.1 unorder_map的介绍
- 5.2 unorder_map的使用
- 5.2.1 unorder_map的构造
- 5.2.2 unorder_map接口的使用
- 5.2.3 使用示例
- 6. unordered_multimap
- 6.1 使用示例
- 7. unordered_map和map的使用差异
unordered_set和unordered_map的使用
1. unordered系列关联式容器
在 C++98 中,STL 提供了底层为红黑树结构的一系列关联式容器,在查询时的效率可达到 O(logN)O(logN)O(logN) ,即最差情况下需要比较红黑树的高度次,当树中的结点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在 C++11 中,STL 又提供了4个 unordered 系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。
2. unorder_set
2.1 unorder_set的介绍
unordered_set 的声明如下:
template < class Key, //
unordered_set::key_type/value_typeclass Hash = hash<Key>, // unordered_set::hasherclass Pred = equal_to<Key>, // unordered_set::key_equalclass Alloc = allocator<Key> // unordered_set::allocator_type> class unordered_set;
- Key 就是 unordered_set 底层关键字的类型。
- unordered_set 默认要求 Key 支持转换为整形,如果不支持或者想按自己的需求实现可以将 Key 转成整形的哈希函数传给第二个模板参数。
- unordered_set 默认要求 Key 支持比较相等,如果不支持或者想按自己的需求实现可以将 Key 比较相等的函数传给第三个模板参数。
- unordered_set 底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第四个参数。
- 一般情况下,都不需要传后三个模板参数。
unordered_set 底层是用哈希桶实现,增删查平均效率是 O(1)O(1)O(1),迭代器遍历不再有序,为了跟 set 区分,所以取名 unordered_set。
前面部分已经学习了 set 容器的使用,set 和 unordered_set 的功能高度相似,只是底层结构不同,有一些性能和使用的差异,这里只介绍它们的差异部分。
2.2 unorder_set的使用
2.2.1 unordered_set的定义方式
方式一:默认构造。
unordered_set<int> us1; //构造int类型的空容器
方式二:拷贝构造。
unordered_set<int> us2(us1); //拷贝构造同类型容器us1的复制品
方式三: 迭代器拷贝构造。
string str("abcedf");
unordered_set<char> us3(str.begin(), str.end()); //构造string对象某段区间的复制品
方式四:initializer list构造
unordered_set<int> u4 = { 10, 20, 30, 40, 50 };
2.2.2 unorder_set接口的使用
unordered_set当中常用的成员函数如下:
成员函数 | 功能 |
---|---|
insert | 插入指定元素 |
erase | 删除指定元素 |
find | 查找指定元素 |
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定元素的元素个数 |
unordered_set当中迭代器相关函数如下:
成员函数 | 功能 |
---|---|
begin | 获取容器中第一个元素的正向迭代器 |
end | 获取容器中最后一个元素下一个位置的正向迭代器 |
2.2.3 使用示例
#include <iostream>
#include <unordered_set>
using namespace std;int main()
{// 默认构造unordered_set<int> us;// 插入元素(去重)us.insert(1);us.insert(4);us.insert(3);us.insert(3);us.insert(2);us.insert(2);us.insert(3);// 遍历容器方式一(范围for)for (auto e : us){cout << e << " ";}cout << endl; //1 4 3 2// 删除元素方式一us.erase(3);// 删除元素方式二unordered_set<int>::iterator pos = us.find(1); //查找值为1的元素if (pos != us.end()){us.erase(pos);}// 遍历容器方式二(迭代器遍历)unordered_set<int>::iterator it = us.begin();while (it != us.end()){cout << *it << " ";it++;}cout << endl; //4 2//容器中值为2的元素个数cout << us.count(2) << endl; //1//容器大小cout << us.size() << endl; //2//清空容器us.clear();//容器判空cout << us.empty() << endl; //1//交换两个容器的数据unordered_set<int> tmp{ 11, 22, 33, 44 };us.swap(tmp);for (auto e : us){cout << e << " ";}cout << endl; //11 22 33 44return 0;
}
3. unordered_multiset
unordered_multiset 容器与 unordered_set 容器的底层数据结构是一样的,都是哈希表,其次,它们所提供的成员函数的接口都是基本一致的,这里就不再列举了,这两种容器唯一的区别就是,unordered_multiset 容器允许键值冗余,即 unordered_multiset 容器当中存储的元素是可以重复的。
3.1 使用示例
#include <iostream>
#include <unordered_set>
using namespace std;int main()
{// 默认构造unordered_multiset<int> ums;// 插入元素(允许重复)ums.insert(1);ums.insert(4);ums.insert(3);ums.insert(3);ums.insert(2);ums.insert(2);ums.insert(3);// 遍历容器for (auto e : ums){cout << e << " ";}cout << endl; //1 4 3 3 3 2 2return 0;
}
由于 unordered_multiset 容器允许值重复,因此该容器中成员函数 find 和 count 的意义与 unordered_set 容器中的也有所不同:
成员函数find | 功能 |
---|---|
unordered_set容器 | 返回键值为val的元素的迭代器 |
unordered_multiset容器 | 返回底层哈希表中第一个找到的键值为val的元素的迭代器 |
成员函数count | 功能 |
---|---|
unordered_set 容器 | 键值为val的元素存在则返回1,不存在则返回0(find成员函数可替代) |
unordered_multiset容器 | 返回键值为val的元素个数(find成员函数不可替代)返回键值为val的元素个数(find成员函数不可替代) |
4. unordered_set和set的使用差异
-
**差异一:**unordered_set 和 set 的第一个差异是对 key 的要求不同,set 要求 Key 支持小于比较,而 unordered_set 要求 Key 支持哈希值且支持等于比较,要理解 unordered_set 的这两个点要求得后续结合哈希表底层实现来理解,也就是说这本质是哈希表的要求。
-
**差异二:**unordered_set 和 set 的第二个差异是迭代器的差异,set 的 iterator 是双向迭代器,unordered_set 是单向迭代器,其次 set 底层是红黑树,红黑树是二叉搜索树,走中序遍历是有序的,所以 set 迭代器遍历是无序+去重。而 unordered_set 底层是哈希表,迭代器遍历是无序+去重。
-
**差异三:**unordered_set 和 set 的第三个差异是性能的差异,整体而言大多数场景下, unordered_set 的增删查改更快一些,因为红黑树增删查改效率是O(logN)O(logN)O(logN),而哈希表增删查改效率是O(1)O(1)O(1)。
5. unorder_map
5.1 unorder_map的介绍
unordered_map 的声明如下:
template < class Key, //unordered_map::key_typeclass T, // unordered_map::mapped_typeclass Hash = hash<Key>, // unordered_map::hasherclass Pred = equal_to<Key>, // unordered_map::key_equalclass Alloc = allocator< pair<const Key,T> > // unordered_map::allocator_type> class unordered_map;
- Key 就是 unordered_map 底层键的类型,T 是与该键配对的值的类型。
- unordered_set 默认为要求 Key 支持转换为整形,如果不支持或者者想按自己的需求走可以自行实现支持将 Key 转成整形的函数传给第三个模板参数
- unordered_set 默认为要求 Key 支持比较相等,如果不支持或者者想按自己的需求可以自行实现支持将 Key 比较相等的函数传给第四个模板参数
- unordered_set 底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第五个参数。
- 一般情况下,都不需要传后三个模板参数
5.2 unorder_map的使用
5.2.1 unorder_map的构造
方式一: 默认构造。
unordered_map<int, double> um1; //构造一个key为int类型,value为double类型的空容器
方式二: 拷贝构造。
unordered_map<int, double> um2(um1); //拷贝构造同类型容器um1的复制品
方式三: 迭代器构造。
unordered_map<int, double> um3(um2.begin(), um2.end()); //使用迭代器拷贝构造um2容器某段区间的复制品
方式四:initializer list构造
unordered_map<std::string, int> um4 = {{ "Beijing", 2189 }, // 每一个花括号对 {key, value} 就是一个元素{ "Shanghai", 2487 },{ "Tokyo", 1400 },{ "New York", 839 }
};
5.2.2 unorder_map接口的使用
unordered_map当中常用的成员函数如下:
成员函数 | 功能 |
---|---|
insert | 插入键值对 |
erase | 删除指定key值的键值对 |
find | 查找指定key值的键值对 |
size | 获取容器中元素的个数 |
empty | 判断容器是否为空 |
clear | 清空容器 |
swap | 交换两个容器中的数据 |
count | 获取容器中指定key值的元素个数 |
operator[]重载函数:
除了上述的成员函数之外,unordered_map容器当中还实现了[ ]运算符重载函数,该重载函数的功能非常强大:[key]
- 若当前容器中已有键值为key的键值对,则返回该键值对value的引用。
- 若当前容器中没有键值为key的键值对,则先插入键值对<key, value()>,然后再返回该键值对中value的引用。
5.2.3 使用示例
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 默认构造unordered_map<int, string> um;//插入键值对方式一:构造匿名对象插入um.insert(pair<int, string>(1, "one"));um.insert(pair<int, string>(2, "two"));um.insert(pair<int, string>(3, "three"));//遍历方式一:范围forfor (auto e : um){cout << e.first << "->" << e.second << " ";}cout << endl; //1->one 2->two 3->three//插入键值对方式二:调用make_pair函数模板插入um.insert(make_pair(4, "four"));um.insert(make_pair(5, "five"));um.insert(make_pair(6, "six"));//遍历方式二:迭代器遍历unordered_map<int, string>::iterator it = um.begin();while (it != um.end()){cout << it->first << "->" << it->second << " ";it++;}cout << endl; //1->one 2->two 3->three 4->four 5->five 6->six//插入键值对方式三:利用[]运算符重载函数进行插入(常用)um[7] = "seven";um[8] = "eight";um[9] = "nine";for (auto e : um){cout << e.first << "->" << e.second << " ";}cout << endl; //9->nine 1->one 2->two 3->three 4->four 5->five 6->six 7->seven 8->eight//删除键值对方式一:根据key值删除um.erase(5);//删除键值对方式二:根据迭代器删除unordered_map<int, string>::iterator pos = um.find(7); //查找键值为7的键值对if (pos != um.end()){um.erase(pos);}for (auto e : um){cout << e.first << "->" << e.second << " ";}cout << endl; //9->nine 1->one 2->two 3->three 4->four 6->six 8->eight//修改键值对方式一:通过find获得迭代器,通过迭代器修改pos = um.find(1);if (pos != um.end()){pos->second = "one/first";}//修改键值对方式二:利用[]运算符重载函数进行修改(常用)um[2] = "two/second";for (auto e : um){cout << e.first << "->" << e.second << " ";}cout << endl; //9->nine 1->one/first 2->two/second 3->three 4->four 6->six 8->eight//容器中key值为3的键值对的个数cout << um.count(3) << endl;//容器的大小cout << um.size() << endl;//清空容器um.clear();//容器判空cout << um.empty() << endl;//交换两个容器的数据unordered_map<int, string> tmp{ { 2021, "before" }, { 2022, "now" } };um.swap(tmp);for (auto e : um){cout << e.first << "->" << e.second << " ";}cout << endl; //2021->before 2022->nowreturn 0;
}
6. unordered_multimap
unordered_multimap 容器与 unordered_map 容器的底层数据结构是一样的,都是哈希表,其次,它们所提供的成员函数的接口都是基本一致的,这里就不再列举了,这两种容器唯一的区别就是,unordered_multimap 容器允许键值冗余,即 unordered_multimap 容器当中存储的键值对的 key 值是可以重复的。
6.1 使用示例
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;int main()
{// 默认构造unordered_multimap<int, string> umm;// 插入键值对(允许键值重复)umm.insert(make_pair(2022, "吃饭"));umm.insert(make_pair(2022, "睡觉"));umm.insert(make_pair(2022, "敲代码"));for (auto e : umm){cout << e.first << "->" << e.second << " ";}cout << endl; //2022->吃饭 2022->睡觉 2022->敲代码return 0;
}
由于 unordered_multimap 容器允许键值对的键值冗余,因此该容器中成员函数find和count的意义与unordered_map容器中的也有所不同:
成员函数find | 功能 |
---|---|
unordered_map 容器 | 返回键值为key的键值对的迭代器 |
unordered_multimap 容器 | 返回底层哈希表中第一个找到的键值为key的键值对的迭代器 |
成员函数count | 功能 |
---|---|
unordered_map 容器 | 键值为key的键值对存在则返回1,不存在则返回0(find成员函数可替代) |
unordered_multimap 容器 | 返回键值为key的键值对的个数(find成员函数不可替代) |
**补充:**其次,由于 unordered_multimap 容器允许键值对的键值冗余,调用 [ ]
运算符重载函数时,应该返回键值为 key 的哪一个键值对的 value 的引用存在歧义,因此在 unordered_multimap 容器当中没有实现 [ ]
运算符重载函数。
7. unordered_map和map的使用差异
-
**差异一:**unordered_map 和 map 的第一个差异是对 key 的要求不同,map 要求 Key 支持比较,而 unordered_map 要求 Key 支持转化成整型且支持等于比较。要理解 unordered_map 的这两个要求,得结合哈希表底层才能真正理解,也就是说这本质是哈希表的要求。
-
**差异二:**unordered_map 和 map 的第二个差异是迭代器的差异,map 的 iterator 是双向迭代器,unordered_map 是单向迭代器,其次 map 底层是红黑树,红黑树是二叉搜索树,走中序遍历是有序的,所以 map 迭代器遍历是 Key 有序+去重。而 unordered_map 底层是哈希表,迭代器遍历是 Key 无序+去重。
-
**差异三:**unordered_map 和 map 的第三个差异是性能的差异,整体而言多数场景下,unordered_map 的增删查改效率是 O(logN)O(log N)O(logN),而 hash 表增删查改效率是 O(1)O(1)O(1)。