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++类和对象之多态详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12
  • VC++6.0实现直线扫描转换的图文教程

    VC++6.0实现直线扫描转换的图文教程

    这篇文章主要给大家介绍了关于VC++6.0实现直线扫描转换的相关资料,文中通过图文将实现的步骤一步步介绍的非常详细,对大家学习或者使用VC++6.0具有一定的参考学习价值,需要的朋友可以参考下
    2023-01-01
  • 详解C++ sizeof(下)

    详解C++ sizeof(下)

    这篇文章主要介绍了C++ sizeof的相关资料,帮助大家更好的理解和学习c++,感兴趣的朋友可以了解下
    2020-08-08
  • C语言 以数据块的形式读写文件详解及实现代码

    C语言 以数据块的形式读写文件详解及实现代码

    本文主要介绍 C语言 以数据块的形式读写文件,这里对相关知识资料做了整理,并附代码示例,以便大家学习参考,有学习此部分知识的朋友可以参考下
    2016-08-08
  • CLion安装、汉化、配置图文详解

    CLion安装、汉化、配置图文详解

    这篇文章主要介绍了CLion安装、汉化、激活、配置图文详解,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • C++中strlen(),sizeof()与size()的区别

    C++中strlen(),sizeof()与size()的区别

    本文主要介绍了C++中strlen(),sizeof()与size()的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • c/c++ 利用sscanf进行数据拆分操作

    c/c++ 利用sscanf进行数据拆分操作

    这篇文章主要介绍了c/c++ 利用sscanf进行数据拆分操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • c++中为什么不提倡使用vector示例详解

    c++中为什么不提倡使用vector示例详解

    这篇文章主要给大家介绍了关于c++中为什么不提倡使用vector的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用c++具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-08-08
  • C++实现移动立方体示例讲解

    C++实现移动立方体示例讲解

    这篇文章主要介绍了C++实现移动立方体,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2022-12-12
  • C++实现扫雷、排雷小游戏

    C++实现扫雷、排雷小游戏

    这篇文章主要为大家详细介绍了C++实现扫雷、排雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05

最新评论