C++STL详解(六)unordered_set&unordered_map介绍

前言

其实unordered_set&unordered_map和set、map的使用基本没有啥区别,会用set、map就肯定会用unordered_set&unordered_map

1.unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时的效率可达到 O ( l o g N ) O(logN) O(logN) ,即最差情况下需要比较红黑树的高度次,当树中的结点非常多时,查询效率也不理想。最好的查询是,进行常数的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同。

2.unordered_set&unordered_map

介绍

unordered_set 文档

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8V2xykpM-1663411178096)(Image/Pasted%20image%2020220908181352.png)]

unordered_map 文档

在这里插入图片描述

unordered_xxx 对比 set、map

容器底层数据结构是否有序实现版本效率迭代器
unordered_xxx哈希桶遍历无序C++11O(1)单向迭代器
map、set红黑树遍历有序C++98O(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 的效率还是挺不错的,不过因为底层是哈希实现,在查找上的优势是更大的。

unordered_multixxx

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[]

尾声

🌹🌹🌹

写文不易,如果有帮助烦请点个赞~ 👍👍👍

Thanks♪(・ω・)ノ🌹🌹🌹

😘😘😘

👀👀由于笔者水平有限,在今后的博文中难免会出现错误之处,本人非常希望您如果发现错误,恳请留言批评斧正,希望和大家一起学习,一起进步ヽ( ̄ω ̄( ̄ω ̄〃)ゝ,期待您的留言评论。
附GitHub仓库链接

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值