【C++】哈希——unordered系列容器&哈希概念&哈希冲突(上)

本文涉及的产品
对象存储 OSS,标准 - 本地冗余存储 20GB 3个月
对象存储OSS,敏感数据保护2.0 200GB 1年
对象存储 OSS,内容安全 1000 次 1年
简介: 【C++】哈希——unordered系列容器&哈希概念&哈希冲突(上)

1. unordered系列的关联式容器


1.1 引言

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到l o g 2 N log_2 Nlog

2

N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个 unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍, unordered_multimap和unordered_multimap可以去自行查看使用文档cplusplus

注意:实际上,unordered系列容器的使用和map/set基本一致,区别就在于unordered系列只支持单向迭代器,并且由于结构的限制,遍历unordered系列容器的顺序是无序


1.2 unordered_map的使用说明

使用文档

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lh9sfZMp-1684930297303)(https://pic-bed123.oss-cn-nanjing.aliyuncs.com/%E6%88%AA%E5%B1%8F2023-05-23%2013.41.16.png)]

void Test_unordered_map()
{
    unordered_map<string, int> countMap;
    string arr[] = { "苹果", "西瓜", "香蕉","苹果", "西瓜", "西瓜"};
    for(const auto& str : arr)
    {
        //countMap[str]++;
        auto it = countMap.find(str);
        if(it == countMap.end())
        {
            countMap.insert(make_pair(str, 1));
        }
        else
        {
            it->second++;
        }
    }
    for(const auto& kv : countMap)
    {
        cout << kv.first << ":" << kv.second << endl;
    }
    cout << endl;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQ3hv30X-1684930297304)(https://pic-bed123.oss-cn-nanjing.aliyuncs.com/%E6%88%AA%E5%B1%8F2023-05-23%2013.48.12.png)]


除了map的接口之外,由于unordered_map的底层是哈希桶,所以,有一些只属于哈希桶的接口

函数原型 功能介绍
size_t bucket_count() const 返回哈希桶中桶的总个数
size_t max_bucket_count() const 返回哈希桶中能够容纳的最大的桶个数
size_t bucket_size(size_t n) const 返回哈希桶中有效元素个数
**size_t bucket(const K& key) ** 返回元素key所在的桶号


1.3 unordered_set的使用说明

使用文档

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l4MBpovo-1684930297304)(https://pic-bed123.oss-cn-nanjing.aliyuncs.com/%E6%88%AA%E5%B1%8F2023-05-23%2013.33.08.png)]

void Test_unordered_set()
{
    unordered_set<int> us;
    us.insert(10);
    us.insert(2);
    us.insert(4);
    us.insert(5);
    us.insert(3);
    us.insert(1);
    us.insert(10);
    unordered_set<int>::iterator it = us.begin();
    while(it != us.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZHaJAyB-1684930297304)(https://pic-bed123.oss-cn-nanjing.aliyuncs.com/%E6%88%AA%E5%B1%8F2023-05-23%2014.04.56.png)]


1.4 unordered_set和unordered_map的应用


在长度为2*N的数组中找到重复出现N次的数


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xTk0r0sx-1684930297305)(https://pic-bed123.oss-cn-nanjing.aliyuncs.com/%E6%88%AA%E5%B1%8F2023-05-23%2014.44.00.png)]


1.5 性能比较

void Test_efficient()
{
  const size_t N = 1000000;//测试的数据个数
    //构造两个数组,一个有序一个无序,用于测试
  vector<int> SortV;
    vector<int> UnsortV;
  SortV.reserve(N);
  UnsortV.reserve(N);
  srand(time(0));
  for (int i = 0; i < N; ++i)
  {
    UnsortV.push_back(rand());
        SortV.push_back(i);
  }
    //开始测试
    unordered_set<int> SortUs;
    unordered_set<int> UnsortUs;
  set<int> SortS;
  set<int> UnsortS;
    cout << "有序插入效率对比" << endl;
  size_t begin1 = clock();
  for (auto e : SortV)
  {
    SortS.insert(e);
  }
  size_t end1 = clock();
  cout << "set insert sorted:" << end1 - begin1 << endl;
  size_t begin2 = clock();
  for (auto e : SortV)
  {
    SortUs.insert(e);
  }
  size_t end2 = clock();
  cout << "unordered_set insert sorted:" << end2 - begin2 << endl;
    cout << "无序插入效率对比" << endl;
  size_t begin3 = clock();
  for (auto e : UnsortV)
  {
    UnsortS.insert(e);
  }
  size_t end3 = clock();
  cout << "set insert unsort:" << end3 - begin3 << endl;
  size_t begin4 = clock();
  for (auto e : UnsortV)
  {
    UnsortUs.insert(e);
  }
  size_t end4 = clock();
  cout << "unordered_set insert unsort:" << end4 - begin4 << endl;
    cout << "有序查找效率对比" << endl;
    size_t begin5 = clock();
  for (auto e : SortV)
  {
    SortS.find(e);
  }
  size_t end5 = clock();
  cout << "set find sorted:" << end5 - begin5 << endl;
  size_t begin6 = clock();
  for (auto e : SortV)
  {
    SortUs.find(e);
  }
  size_t end6 = clock();
  cout << "unordered_set find sorted:" << end6 - begin6 << endl;
    cout << "无序查找效率对比" << endl;
    size_t begin7 = clock();
  for (auto e : UnsortV)
  {
    UnsortS.find(e);
  }
  size_t end7 = clock();
  cout << "set find unsorted:" << end7 - begin7 << endl;
  size_t begin8 = clock();
  for (auto e : UnsortV)
  {
    UnsortUs.find(e);
  }
  size_t end8 = clock();
  cout << "unordered_set find unsorted:" << end8 - begin8 << endl;
    cout << "有序删除效率对比" << endl;
  size_t begin9 = clock();
  for (auto e : SortV)
  {
    SortS.erase(e);
  }
  size_t end9 = clock();
  cout << "set erase sorted:" << end9 - begin9 << endl;
  size_t begin10 = clock();
  for (auto e : SortV)
  {
    SortUs.erase(e);
  }
  size_t end10 = clock();
  cout << "unordered_set erase:" << end10 - begin10 << endl;
    cout << "无序删除效率对比" << endl;
    size_t begin11 = clock();
  for (auto e : UnsortV)
  {
    UnsortS.erase(e);
  }
  size_t end11 = clock();
  cout << "set erase sorted:" << end11 - begin11 << endl;
  size_t begin12 = clock();
  for (auto e : UnsortV)
  {
    UnsortUs.erase(e);
  }
  size_t end12 = clock();
  cout << "unordered_set erase:" << end12 - begin12 << endl;
}


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wXEUyCsZ-1684930297305)(https://pic-bed123.oss-cn-nanjing.aliyuncs.com/%E6%88%AA%E5%B1%8F2023-05-23%2015.07.18.png)]


可以看到随机数下unordered系列效率更高,但是有序数的情况下就不太行。


2. 哈希概念


unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构,那么什么是哈希?


顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素 时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即**O(l o g 2 N log_2 Nlog

2

N)**搜索的效率取决于搜索过程中元素的比较次数。


理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。类似通过下标访问数组中任意位置的元素。 如果构造一种存储结构,通过某种函数(HashFunc)使元素的存储位置与它的关键码之间能够建立 一一映射的关系,那么在查找时通过该函数可以很快找到该元素。


当向该结构中插入元素时:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放;搜索元素时:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功.

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hash Table)(或者称散列表)


接下来我们看下面这个例子:

假设有数据集{1, 7, 6, 4, 5, 9};

哈希函数设置为:hash(key) = key % capacity; capacity为存储元素底层空间总的大小

按照这种存放方式,如果需要查找4,就直接通过4 % 10 = 4找到hash(key) = 4的位置,直接取出来即可,省略了多次key的比较,节约了时间。

a0d13a663c124326b054b64d1c5d87b4.png

相关实践学习
通义万相文本绘图与人像美化
本解决方案展示了如何利用自研的通义万相AIGC技术在Web服务中实现先进的图像生成。
相关文章
|
存储 算法 Serverless
【C++】unordered系列
`unordered`容器是C++11及其后续版本中STL的一部分,包括`unordered_map`和`unordered_set`,基于哈希表实现,支持快速查找、插入和删除操作。`unordered_map`存储键值对,`unordered_set`存储唯一元素,两者均提供高效的存取性能,特别适合处理大数据量的应用场景。这些容器通过哈希函数将数据映射到特定位置,虽然存在哈希冲突问题,但可通过开放定址法、链地址法等策略有效解决。
226 3
|
10月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
9月前
|
存储 编译器 C++
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
1130 0
|
12月前
|
关系型数据库 MySQL Docker
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
639 24
|
12月前
|
关系型数据库 MySQL Docker
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
《docker高级篇(大厂进阶):5.Docker-compose容器编排》包括是什么能干嘛去哪下、Compose核心概念、Compose使用三个步骤、Compose常用命令、Compose编排微服务
616 6
|
存储 搜索推荐 C++
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
236 9
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器2
|
存储 C++ 容器
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器1
【C++篇】深度剖析C++ STL:玩转 list 容器,解锁高效编程的秘密武器
238 5
|
存储 设计模式 C++
【C++】优先级队列(容器适配器)
本文介绍了C++ STL中的线性容器及其适配器,包括栈、队列和优先队列的设计与实现。详细解析了`deque`的特点和存储结构,以及如何利用`deque`实现栈、队列和优先队列。通过自定义命名空间和类模板,展示了如何模拟实现这些容器适配器,重点讲解了优先队列的内部机制,如堆的构建与维护方法。
179 0
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
236 2
|
存储 算法 C++
【算法】哈希映射(C/C++)
【算法】哈希映射(C/C++)

热门文章

最新文章