C++11引入的STL中的unordered系列关联式容器

 更新时间:2025年10月03日 09:32:55   作者:oi嘉菲猫  
STL中的unordered系列容器是C++11引入的基于哈希表实现的关联式容器,STL unordered系列容器基于哈希表,支持快速查找但遍历效率低,适用于无序场景;树形容器基于红黑树,有序且性能稳定,适用于需要有序的场景

STL中的unordered系列容器是C++11引入的基于哈希表实现的关联式容器,与传统的红黑树实现的关联容器(map/set)相比,它们在元素存储和访问机制上有显著差异

1.补充认识

STL中的unordered系列容器的底层就是哈希表,包括unordered_set、unordered_map、unordered_multiset、unordered_multimap四个。

底层哈希原理

补充:从底层来说应该分别类似的命名为tree_map和hash_map,也就是树形结构和哈希结构的,但是由于map之类的出来的早,后面的为了对应就把hash实现的命名为unordered_xx。

2.unordered_set

2.1容器原型

C++——16.STL的unordered系列容器_迭代器

  • unordered_set是存储key键值的关联式容器,其允许通过keys快速的索引到与其对应的value。
  • 在unordered_set中,键值通常用于惟一地标识元素。
  • 在内部,unordered_set没有对kye按照任何特定的顺序排序, 为了能在常数范围内找到key,unordered_set将相同哈希值的键值放在相同的桶中。
  • unordered_set容器通过key访问单个元素要比set快,但它通常在遍历元素子集的范围迭代方面效率较低。
  • 它的迭代器至少是前向迭代器。

2.2常用接口

C++——16.STL的unordered系列容器_迭代_02

3.unordered_multiset

和set与multiset的区别一样,unordered_multiset相对于unordered_set支持相同关键码的存放。

接口区别:

C++——16.STL的unordered系列容器_迭代器_03

4.unordered_map

4.1容器原型

C++——16.STL的unordered系列容器_迭代_04

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

4.2常用接口

与unordered_set相比,unordered_map就是键值对KV模型,并且提供了[]重载和at接口,这两个与map相似功能。

其他接口功能与unordered_set相同。

5.unordered_multimap

unordered_multimap和unordered_map的区别就是unordered_multiset和unordered_multiset的区别。

另外就是unordered_multimap不支持[]和at。

6.树形关联容器和哈希关联容器的对比

特性

哈希容器

树形容器

底层结构

哈希表(顺序表+顺序表/红黑表)

红黑树(平衡二叉搜索树)

元素顺序

无序

有序

平均时间复杂度

O(1)

O(log_2 N)

最坏时间复杂度

O(N)(所有元素都冲突,实际几乎不可能)

O(log_2 N)

迭代器

单向迭代器

双向迭代器

关键特性

增删查改都很快,遍历较慢。性能依赖哈希函数。

插入后自然有序。

适用场景

快速查找,不关注顺序。

需要元素有序。

小总结:除非有有序的要求,不然都可以先考虑哈希容器。

7.unordered系列模拟实现

7.1重要补充

1)具体存放什么数据类型应该是set和map层面决定的,底层的哈希表只是存放数据,所以需要一个从存放数据提取关键码的KeyOfT反函数。

2)另外还需要一个哈希函数来处理不能直接取模的情况。

3)哈希表的迭代器迭代逻辑:单链表迭代+顺序表的哈希桶迭代。

4)如果使用自定义类型取作为树形关联容器的关键码的话,需要提供一个小于比较仿函数,有了小于也就有了大于和等于。

5)如果使用自定义类型取作为哈希关联容器的关键码的话,需要提供一个等于比较仿函数。

6)set的迭代器无论是普通迭代器还是const迭代器在底层都是使用const迭代器。

7.2代码

源码

7.2.1哈希表

#pragma once 
#include <iostream>
// 使用哈希桶解决哈希表的哈希冲突
#include <vector>
#include <utility>

using namespace std;

namespace lbq
{
    // 将输入转成整型的哈希函数
    template<class K>
    struct HashFunc 
    {
        size_t operator()(const K& key)
        {
            return (size_t)key;
        }
    };
    
    // 针对常用的字符串类型的特化
    template<>
    struct HashFunc<string>
    {
        size_t operator()(const string& str)
        {
            size_t ret = 0;
            for(auto& ele : str)
            {
                ret = ret * 131 + ele;
            }
    
            return ret;
        }
    };

    // 哈希桶具体存储的数据类型
    // 实际为一个单链表节点
    template<class T>
    struct _HashNode
    {
        T _data;
        _HashNode<T>* _next;

        _HashNode(const T& data)
            : _data(data)
            , _next(nullptr)
        {}
    };

    // 哈希表的迭代器,其实是顺序表迭代+链表迭代
    template<class K, class T, class KeyOfT, class Hash > class HashTable;   // 迭代器中需要访问,提前声明
    template<class K, class T, class KeyOfT, class Hash>
    struct _HashTableIterator
    {
        typedef _HashNode<T> Node;
        typedef HashTable<K, T, KeyOfT, Hash> HT;
        typedef _HashTableIterator<K, T, KeyOfT, Hash> Iterator;

        Node* _node;
        HT* _ht;

        _HashTableIterator(Node* node, HT* ht)
            : _node(node)
            , _ht(ht)
        {}

        bool operator==(const Iterator& it)const 
        {
            return _node == it._node;
        }

        bool operator!=(const Iterator& it)const 
        {
            return _node != it._node;
        }

        T& operator*()
        {
            return _node->_data;
        }

        T* operator->()
        {
            return &(_node->_data);
        }

        Iterator operator++()
        {
            if(_node->_next != nullptr)
            {
                _node = _node->_next;
            }
            else 
            {
                // 找下一个桶的位置
                size_t table_index = Hash()(KeyOfT()(_node->_data)) % _ht->_table.size();
                while(++table_index < _ht->_table.size())
                {
                    if(_ht->_table[table_index] != nullptr)
                    {
                        _node = _ht->_table[table_index];
                        break;
                    }
                }

                // 如果后面已经没有桶了
                if(table_index == _ht->_table.size())
                {
                    _node = nullptr;
                }
            }

            return *this;
        }

        Iterator operator++(int)
        {
            Iterator tmp(_node, _ht);
            operator++();
            return tmp;
        }
    };

    // 使用除留余数法作为哈希表的哈希函数,使用哈希桶解决冲突
    // K:关键码数据类型
    // T:存储的数据类型,Key或者pair<K, V>
    // KeyOfT:从存储的数据中提取关键码
    // Hash:将关键码转成整型用于取模运算
    template<class K, class T, class KeyOfT, class Hash>
    class HashTable
    {
    private:
        typedef _HashNode<T> Node;
        friend struct _HashTableIterator<K, T, KeyOfT, Hash>;

    public:
        typedef _HashTableIterator<K, T, KeyOfT, Hash> iterator;

        iterator begin()
        {
            for(size_t i = 0; i < _table.size(); i++)
            {
                if(_table[i] != nullptr)
                {
                    return iterator(_table[i], this);
                }
            }

            return end();
        }

        iterator end()
        {
            return iterator(nullptr, this);
        }

        //HashTable()
        //{
        //    _table.resize(10, nullptr);
        //    _sz = 0;
        //}

        ~HashTable()
        {
            for(size_t i = 0; i < _table.size(); i++)
            {
                Node* cur = _table[i];
                while(cur != nullptr)
                {
                    Node* next = cur->_next;
                    delete cur;
                    cur = next;
                }
                _table[i] = nullptr;
            }
        }

        pair<iterator, bool> insert(const T& data)
        {
            Hash hash;
            KeyOfT kot;
            iterator find_it = find(kot(data));
            if(find_it != end())
            {
                // 已经存在相同的元素
                return make_pair(find_it, false);
            }
            
            if(_table.size() == 0 || _sz == _table.size())
            {
                // 顺序表为空,或者载荷因子大于1时增容
                size_t new_size = _table.size() == 0 "h14">7.2.2unordered_set
#pragma once 
// hash set模拟实现
#include "hashtable.hpp"

namespace lbq
{
    template<class K, class Hasher = HashFunc<K>>
    class unordered_set 
    {
    private:
        struct SetKeyOfT
        {
            const K& operator()(const K& key)
            {
                return key;
            }
        };

        typedef HashTable<K, K, SetKeyOfT, Hasher> HT;

    public:
        typedef typename HT::iterator iterator;

        iterator begin()
        {
            return _ht.begin();
        }

        iterator end()
        {
            return _ht.end();
        }

        bool empty()
        {
            return _ht.empty();
        }

        size_t size()
        {
            return _ht.size();
        }

        iterator find(const K& key)
        {
            return _ht.find(key);
        }

        size_t count(const K& key)
        {
            return _ht.count(key);
        }

        pair<iterator, bool> insert(const K& key)
        {
            return _ht.insert(key);
        }

        bool erase(const K& key)
        {
            return _ht.erase(key);
        }

        size_t bucket_count()
        {
            return _ht.bucket_count();
        }

        size_t bucket_size(size_t n)
        {
            return _ht.bucket_size(n);
        }


    private:
        HT _ht;
    };
}

7.2.3unordered_map

#pragma once 
// unorder_map 模拟实现
#include "hashtable.hpp"

namespace lbq
{
    template<class K, class V, class Hasher = HashFunc<K>>
    class unordered_map 
    {
    private:
        typedef pair<K, V> T;
        struct MapKeyOfT
        {
            const K& operator()(const T& kv)
            {
                return kv.first;
            }
        };
        typedef HashTable<K, T, MapKeyOfT, Hasher> HT;

    public:
        typedef typename HT::iterator iterator;

        iterator begin()
        {
            return _ht.begin();
        }

        iterator end()
        {
            return _ht.end();
        }

        size_t size()
        {
            return _ht.size();
        }

        bool empty()
        {
            return _ht.empty();
        }

        V& operator[](const K& key)
        {
            pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));
            return ret.first->second;
        }

        iterator find(const K& key)
        {
            return _ht.find(key);
        }

        size_t count(const K& key)
        {
            return _ht.count(key);
        }

        pair<iterator, bool> insert(const T& kv)
        {
            return _ht.insert(kv);
        }

        bool erase(const K& key)
        {
            return _ht.erase(key);
        }

        size_t bucket_count()
        {
            return _ht.bucket_count();
        }

        size_t bucket_size(const size_t& n )
        {
            return _ht.bucket_size(n);
        }

    private:
        HT _ht;
    };
}

到此这篇关于C++11引入的STL中的unordered系列关联式容器的文章就介绍到这了,更多相关C++的STL中的unordered容器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言实现学籍管理系统

    C语言实现学籍管理系统

    这篇文章主要为大家详细介绍了C语言实现学籍管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • VC中SendMessage和PostMessage的区别

    VC中SendMessage和PostMessage的区别

    这篇文章主要介绍了VC中SendMessage和PostMessage的区别,较为全面的分析了SendMessage和PostMessage运行原理及用法上的不同之处,非常具有实用价值,需要的朋友可以参考下
    2014-10-10
  • C语言实现模拟USB对8bit数据的NRZI编码输出

    C语言实现模拟USB对8bit数据的NRZI编码输出

    今天小编就为大家分享一篇关于C语言实现模拟USB对8bit数据的NRZI编码输出,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • C++探索构造函数私有化会产生什么结果

    C++探索构造函数私有化会产生什么结果

    C++的构造函数的作⽤:初始化类对象的数据成员。即类的对象被创建的时候,编译系统对该对象分配内存空间,并⾃动调⽤构造函数,完成类成员的初始化。构造函数的特点:以类名作为函数名,⽆返回类型
    2022-05-05
  • Qt中QCommandLinkButton控件的使用

    Qt中QCommandLinkButton控件的使用

    QCommandLinkButton 是 Qt 框架中 QtWidgets 模块的一个类,它提供了一个结合了文本标签和按钮功能的控件,本文主要介绍了Qt中QCommandLinkButton控件的使用,感兴趣的可以了解一下
    2025-04-04
  • C语言单链表实现方法详解

    C语言单链表实现方法详解

    这篇文章主要介绍了C语言单链表实现方法,结合实例形式分析了基于C语言的单链表定义、创建、添加、删除、排序、打印等操作技巧,并附带了相关的优化算法,需要的朋友可以参考下
    2018-04-04
  • C++的程序流程结构你了解多少

    C++的程序流程结构你了解多少

    这篇文章主要为大家详细介绍了C++的程序流程结构,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • Linux下实现C++操作Mysql数据库

    Linux下实现C++操作Mysql数据库

    由于工作需要抽出一周的时间来研究C/C++访问各种数据库的方法,并打算封装一套数据库操作类,现在奉上最简单的一部分:在Linux下访问MySQL数据库。
    2017-05-05
  • C++ 内联函数inline案例详解

    C++ 内联函数inline案例详解

    这篇文章主要介绍了C++ 内联函数inline案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • C++string中的insert()插入函数详解

    C++string中的insert()插入函数详解

    这篇文章主要介绍了C++string中的insert()插入函数,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03

最新评论