当前位置: 首页 > news >正文

【重走C++学习之路】20、unordered_map和unordered_set

目录

一、无序关联式容器

二、unordered_map

2.1 构造函数

2.2 容量操作

2.3 迭代器

2.4 增删查操作

三、unordered_set

3.1 构造函数

3.2 容量操作 

3.3 迭代器 

3.4 增删查操作

四、基于哈希表实现unordered_map和unordered_set

4.1 改造哈希表

1. 基本结构

2. 迭代器实现

 4.2 封装unordered_map和unordered_set

结语


一、无序关联式容器

在 C++ 中,无序关联式容器是一类基于(Key)快速访问元素的容器,其元素没有预设的顺序(不按键排序),而是通过哈希表(Hash Table)实现存储和检索,具有平均情况下 (O(1)) 的插入、删除和查找效率。

主要特点:

  1. 无序性
    元素的存储顺序与键的插入顺序或键值大小无关,迭代器遍历的顺序是不确定的(由哈希表内部结构决定),不支持按顺序访问(如有序容器 map 的按键排序)。

  2. 关联式访问
    每个元素由键值对(key-value,如 unordered_map)或单一键(unordered_set)组成,通过键快速定位元素,属于 “关联式容器”(区别于序列式容器如 vector)。

  3. 哈希表实现

    • 通过哈希函数(Hash Function)将键映射到哈希表的桶(Bucket)中,冲突(不同键映射到同一桶)通过链地址法(桶内用链表或红黑树存储冲突元素)处理。
    • C++ 标准库默认使用 std::hash<Key> 作为哈希函数,支持自定义类型时需提供哈希函数和等于谓词(operator==)。

四种无序关联式容器:

容器描述
std::unordered_set存储 唯一键 的集合,元素无序。
std::unordered_map存储 唯一键值对 的集合,键唯一,元素无序。
std::unordered_multiset存储 可重复键 的集合,元素无序。
std::unordered_multimap存储 可重复键值对 的集合,元素无序。

使用场景:

  • 需要快速查找、插入、删除:如字典、频率统计(用 unordered_map 统计单词出现次数)。
  • 不关心元素顺序:若需要按键排序,应使用有序关联容器(map/set,基于红黑树,时间复杂度 O(logN))

二、unordered_map

主要特点:

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序,为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中。
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  6. 它的迭代器是单向迭代器。

2.1 构造函数

常用的几个:

  • 构造函数

构造空的unordered_map对象

  • 拷贝构造函数

利用unordered_map对象构造新的unordered_map对象

  • 迭代器构造 

利用迭代器将容器中的某个区间来构造新的unordered_map对象

2.2 容量操作

  • empty

判断容器是否为空

  • size

返回容器中的元素个数

 

2.3 迭代器

迭代器的接口都是一致的,需要注意的是这个容器只有正向迭代器(包括const迭代器) 

2.4 增删查操作

  • operator[]

可以通过key找到value

  • find

iterator find ( const key_type& k ),查找的时间复杂度可以达到O(1)

  • insert

pair<iterator,bool> insert ( const value_type& val ),往容器中插入键值对

  • erase

iterator erase ( const_iterator position ),删除容器中的键值对

  • clear

清空容器中的元素

  • swap

交换两个容器中的元素

 

示例:

void test_unordered_map()
{unordered_map<int, int> um;int arr[] = { 4,2,3,1,6,8,9,3 };for (auto e : arr){um.insert(make_pair(e, e));}unordered_map<int, int>::iterator umit = um.begin();while (umit != um.end()){cout << umit->first << ":" << umit->second << endl;++umit;}
}

三、unordered_set

主要特点:

  1. unordered_set是以无特定顺序存储唯一元素的容器,并且允许根据它们的值快速检索单个元素,是一种K模型。
  2. 在unordered_set中,元素的值同时是它的key,它唯一地标识它。键值是不可变的,因unordered_set中的元素不能在容器中修改一次 ,但是可以插入和删除它们。
  3. 在内部,unordered_set中的元素不是按任何特定顺序排序的,而是根据它们的哈希值组织成桶,以允许直接通过它们的值快速访问单个元素,时间复杂度可以达到O(1)。
  4. unordered_set容器比set容器更快地通过它们的key访问单个元素,尽管它们通常对于通过其元素的子集进行范围迭代的效率较低。
  5. 容器中的迭代器至少是单向迭代器。

3.1 构造函数

  • 构造函数

构造空的unordered_set对象

  • 拷贝构造函数

利用unordered_set对象构造新的unordered_set对象

  • 迭代器构造 

利用迭代器将容器中的某个区间来构造新的unordered_set对象

 

3.2 容量操作 

  •  empty

判断容器是否为空

  • size

返回容器中的元素个数

3.3 迭代器 

同 unordered_map一样只有正向迭代器。

3.4 增删查操作

  • find

iterator find ( const key_type& k ),查找的时间复杂度可以达到O(1)

  • insert

pair<iterator,bool> insert ( const value_type& val ),往容器中插入key

  • erase

iterator erase ( const_iterator position ),删除容器中的key

  • clear

清空容器中的元素

  • swap

交换两个容器中的元素

示例:

void test_unordered_set()
{unordered_set<int> us;int arr[] = { 4,2,3,1,6,8,9,3 };for (auto e : arr){us.insert(e);}unordered_set<int>::iterator usit = us.begin();while (usit != us.end()){cout << *usit << " ";++usit;}
}

总结: unordered_map和unordered_set基本上相差不大,就是unordered_map多了一个operator[]的操作,支持通过key找到value的操作。而且与map和set的操作也类似,只是底层在存储结构上有差异。

四、基于哈希表实现unordered_map和unordered_set

这里利用开散列的哈希表来实现unordered_map和unordered_set,有关哈希内容可以去我的上一篇文章查看【重走C++学习之路】19、哈希

4.1 改造哈希表

1. 基本结构

template<class T>
struct HashNode
{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}
};template<class K, class T, class KOFV, class Hash>
class HashTable
{typedef HashNode<T> Node;private:vector<Node*> _tables;int _num = 0;// 记录表中的数据个数
};

第一个和第二个模板参数跟map和set的是实现类似,为了将key和pair<key,value>封装在一起;第三个模板参数是为了统一取数操作;第四个模板参数是哈希函数。

2. 迭代器实现

template<class K, class T, class Ref, class Ptr, class KOFV, class Hash>
struct __HashTable_Iterator
{typedef __HashTable_Iterator<K, T, Ref, Ptr, KOFV, Hash> Self;typedef HashNode<T> Node;typedef HashTable<K, T, KOFV, Hash> HashTable;Node* _node;HashTable* _ht;__HashTable_Iterator(Node* node, HashTable* ht):_node(node),_ht(ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_next){_node = _node->_next;return *this;}else{KOFV kofv;int index = _ht->HashFunc(kofv(_node->_data)) % _ht->_tables.size();for (size_t i = index + 1; i < _ht->_tables.size(); ++i){if (_ht->_tables[i]){_node = _ht->_tables[i];return *this;}}_node = nullptr;return *this;}		}bool operator==(const Self& self) const{return _node == self._node&& _ht == self._ht;}bool operator!=(const Self& self) const{return !this->operator==(self);}
};

3. 哈希表内部修改

template<class K, class T, class KOFV, class Hash>
class HashTable
{typedef HashNode<T> Node;friend struct __HashBucket_Iterator<K, T, T&, T*, KOFV, Hash>;// 为了让迭代器中能够使用HashFunc这个函数public:typedef __HashTable_Iterator<K, T, T&, T*, KOFV, Hash> iterator;iterator begin(){for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i] != nullptr)return iterator(_tables[i], this);// 哈希桶的第一个节点 }return end();// 没有节点返回最后一个迭代器}iterator end(){return iterator(nullptr, this);}
};

 4.2 封装unordered_map和unordered_set

template<class K, class V, class Hash = _Hash<K>>
class unordered_map
{struct MapKeyOfValue{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef HashTable<K, pair<K, V>, MapKeyOfValue, Hash> HashTable;public:// 告诉编译器这只是一个类型typedef typename HashBucket::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}bool erase(const K& key){return _ht.Erase(key);}iterator find(const K& key){return _ht.Find(key);}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}private:HashBucket _ht;
};template<class K, class Hash = _Hash<K>>
class unordered_set
{struct SetKeyOfValue{const K& operator()(const K&  key){return key;}};typedef HashTable<K, K, SetKeyOfValue, Hash> HashTable;
public:// 告诉编译器这只是一个类型typedef typename HashBucket::iterator iterator;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator, bool> insert(const K& kv){return _ht.Insert(kv);}bool erase(const K& key){return _ht.Erase(key);}iterator find(const K& key){return _ht.Find(key);}private:HashBucket _ht;
};

结语

这两个容器应用在后续的项目和做题中用的比较多,可以在O(1)的时间复杂度完成操作,这对效率的提升是巨大的。除此之外,在map和unordered_map这两者之间,基本上后者用的比较多。

下一篇将会介绍位图和布隆过滤器这两个处理海量元素的哈希应用,有兴趣的朋友可以关注一下。

相关文章:

  • 跨境支付接口RT从300ms突增至2000ms,但CPU/Memory无异常,如何排查?
  • 第二大脑-个人知识库
  • 使用FME生成Delaunay三角形
  • MobX 在 React 中的使用:状态管理的新选择
  • Native层Trace监控性能
  • C语言高频面试题——指针赋值字符串与定义一个数组赋值字符串有什么区别?
  • Pygame精灵进阶:动画序列与角色控制
  • Docker中修改OpenJDK 17 TLS禁用算法
  • 数据分析管理软件 Minitab 22.2.2 中文版安装包 免费下载
  • gtest 安装及使用
  • GPU 加速库(CUDA/cuDNN)
  • 2025年暨南大学 ACM校赛分析与题解
  • 数据结构顺序表的实现
  • react 报错
  • TortoiseGit 入门指南
  • [特殊字符] 深入理解Spring Cloud与微服务架构:全流程详解(含中间件分类与实战经验)
  • 什么是函数依赖中的 **自反律(Reflexivity)**、**增广律(Augmentation)** 和 **传递律(Transitivity)?
  • 大模型奖励建模新突破!Inference-Time Scaling for Generalist Reward Modeling
  • Python爬虫-爬取汽车之家各品牌月销量榜数据
  • Pygame终极项目:从零开发一个完整2D游戏
  • 美国季度GDP时隔三年再现负增长,特朗普政府关税政策对美国经济负面影响或将持续
  • 解放日报:人形机器人新赛道正积蓄澎湃动能
  • 解放日报:抢占科技制高点,赋能新质生产力
  • 韩国检方结束对尹锡悦私宅的扣押搜查
  • 辽宁辽阳市白塔区一饭店发生火灾,当地已启动应急响应机制
  • 解放日报:这是一场需要定力和实力的“科技长征”