赞
踩
其实unordered_set&unordered_map和set、map的使用基本没有啥区别,会用set、map就肯定会用unordered_set&unordered_map
在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时的效率可达到 O ( l o g N ) O(logN) O(logN) ,即最差情况下需要比较红黑树的高度次,当树中的结点非常多时,查询效率也不理想。最好的查询是,进行常数的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。
容器 | 底层数据结构 | 是否有序 | 实现版本 | 效率 | 迭代器 |
---|---|---|---|---|---|
unordered_xxx | 哈希桶 | 遍历无序 | C++11 | O(1) | 单向迭代器 |
map、set | 红黑树 | 遍历有序 | C++98 | O(logN) | 双向迭代器 |
总的来说,会用 set、map 就肯定会用 unordered_set 、unordered_map。
void test_set1() { unordered_set<int> s; //set<int> s; s.insert(2); s.insert(3); s.insert(1); s.insert(2); s.insert(5); //unordered_set<int>::iterator it = s.begin(); auto it = s.begin(); // unordered_set 只有单向迭代器,也就是没有rbegin、reend等等 while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; // 2 3 1 5 for (auto e : s) { cout << e << " "; } cout << endl; // 2 3 1 5 }
set 的迭代器是双向迭代器,而 unordered_set 的是单向迭代器。
一、有大量重复数据的情况
void test_op() { int n = 10000000; vector<int> v; v.reserve(n); srand(time(0)); for (int i = 0; i < n; ++i) { v.push_back(rand()); // 重复多 } size_t begin1 = clock(); set<int> s; for (auto e : v) { s.insert(e); } size_t end1 = clock(); size_t begin2 = clock(); unordered_set<int> us; for (auto e : v) { us.insert(e); } size_t end2 = clock(); cout << s.size() << endl; cout << "set insert:" << end1 - begin1 << endl; cout << "unordered_set insert:" << end2 - begin2 << endl; }
注意:rand 生成的伪随机数范围是有限制的,最大也就是 RAND_MAX 。
此时大体可以看出unordered_set的效率比set效率高。
二、大量随机数据的情况
void test_op() { int n = 10000000; vector<int> v; v.reserve(n); srand(time(0)); for (int i = 0; i < n; ++i) { v.push_back(rand() + i); // 重复少 } size_t begin1 = clock(); set<int> s; for (auto e : v) { s.insert(e); } size_t end1 = clock(); size_t begin2 = clock(); unordered_set<int> us; for (auto e : v) { us.insert(e); } size_t end2 = clock(); cout << s.size() << endl; cout << "set insert:" << end1 - begin1 << endl; cout << "unordered_set insert:" << end2 - begin2 << endl; }
对于插入,数据量过大之后,unordered_set 效率反而不如 set。
void test_op() { int n = 10000000; vector<int> v; v.reserve(n); srand(time(0)); for (int i = 0; i < n; ++i) { //v.push_back(i); v.push_back(rand()+i); // 重复少 //v.push_back(rand()); // 重复多 } size_t begin1 = clock(); set<int> s; for (auto e : v) { s.insert(e); } size_t end1 = clock(); size_t begin2 = clock(); unordered_set<int> us; for (auto e : v) { us.insert(e); } size_t end2 = clock(); cout << s.size() << endl; cout << "set insert:" << end1 - begin1 << endl; cout << "unordered_set insert:" << end2 - begin2 << endl; size_t begin3 = clock(); for (auto e : v) { s.find(e); } size_t end3 = clock(); size_t begin4 = clock(); for (auto e : v) { us.find(e); } size_t end4 = clock(); cout << "set find:" << end3 - begin3 << endl; cout << "unordered_set find:" << end4 - begin4 << endl; }
Debug版本下,unordered_set的插入、查找、删除的效率都是较高的。
而在release版本下,查找的效率都被优化到了O(1),其实也不难理解。一个O(logN)一个O(1)
除非数据量及其大,不然是很难看出明显区别的。
void test_op() { int n = 10000000; vector<int> v; v.reserve(n); srand(time(0)); for (int i = 0; i < n; ++i) { //v.push_back(i); //v.push_back(rand()+i); // 重复少 //v.push_back(rand()); // 重复多 v.push_back(rand()*rand()); } size_t begin1 = clock(); set<int> s; for (auto e : v) { s.insert(e); } size_t end1 = clock(); size_t begin2 = clock(); unordered_set<int> us; for (auto e : v) { us.insert(e); } size_t end2 = clock(); cout << s.size() << endl; cout << "set insert:" << end1 - begin1 << endl; cout << "unordered_set insert:" << end2 - begin2 << endl; size_t begin3 = clock(); for (auto e : v) { s.find(e); } size_t end3 = clock(); size_t begin4 = clock(); for (auto e : v) { us.find(e); } size_t end4 = clock(); cout << "set find:" << end3 - begin3 << endl; cout << "unordered_set find:" << end4 - begin4 << endl; size_t begin5 = clock(); for (auto e : v) { s.erase(e); } size_t end5 = clock(); size_t begin6 = clock(); for (auto e : v) { us.erase(e); } size_t end6 = clock(); cout << "set erase:" << end5 - begin5 << endl; cout << "unordered_set erase:" << end6 - begin6 << endl; }
就 erase 而言,unordered_set 的效率也是要高一点的。
总的来说,unordered_xxx 的效率还是挺不错的,不过因为底层是哈希实现,在查找上的优势是更大的。
multi版本是允许键值冗余,也就是可以存相同的元素。
#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 2 return 0; }
find | 介绍 |
---|---|
unordered_set | 返回键值为val的元素的迭代器 |
unordered_multiset | 返回第一个找到的键值为val的元素的迭代器 |
count | 介绍 |
---|---|
unordered_set | 键值为val的元素存在则返回1,不存在则返回0(find成员函数可替代) |
unordered_multiset | 返回键值为val的元素个数(find成员函数不可替代) |
同样,unordered_multimap 的find、count也是类似的。
#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容器允许键值对的键值冗余,调用operator[]
时,应该返回键值为key的哪一个键值对的value的引用存在歧义,因此在unordered_multimap容器当中没有实现operator[]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。