新C++(11):unordered_map\set的封装

news2024/12/24 11:28:17

"假如可以让音乐停下来"

一、unordered_map\unordered_set简介

在C++98中,STL底层提供了以红黑树封装的关联式容器map\set,其查询效率可以达到LogN(以2为底)。而在C++11中,STL又提供了unordered(无序)容器,其使用方式与map\set类似,但其底层不是红黑树的数据结构,而是一种哈希结构。

unordered_set\unordered_map:
①unordered_set的存储设计为<Key>模型,而unordered_map存储的设计为<Key,Value>模型。
②它们都是以Key值 标识一个位置的映射值。
③它们不同于map、set可以顺序遍历,而是一种无序的存储结构。
④unordered_map容器通过key访问单个元素要比map快。但是,如果让哈希结构的unordered_map去查询一段子类集的范围,效率就不如map了。
⑤unordered_map只提供了前向迭代器。


二、哈希结构

Hash,是把任意长度的输入(又叫做预映射pre-image)通过"散列算法"( 哈希函数)变换成固定长度的输出,该输出就是 散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。 取自这里

哈希其实就是一种映射性的数据结构。如何设计好哈希结构,跟我们怎样选择、设计哈希函数息息相关。

常见的哈希函数有以下几种:
1.直接定址法。取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)
2.平方取中法。取关键字平方后的中间几位作为散列地址。
3.随机数法。选择一随机函数,取关键字作为随机函数的种子生成随机值作为散列地址,通常用于关键字长度不同的场合。
4.除留余数法。取关键字被某个不大于散列表(空间)表长m的数p除后所得的余数为散列地址。
即 H(key) = key MOD p,p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生碰撞。 取自这里

我们举一个小小的例子:


三、底层结构

多的不谈,我们现在来看看封装。不过在这之前,我们先来看看STL的源码库。

(1)HashData

HashData同上篇讲的红黑树结点Value一样,它们根本不关心你到底是传入的<Key,Value>模型,还是就是<Key>模型。

    template<class Value>
    struct  HashTable_node
    {
        HashTable_node<Value>* _next;
        Value _data;        
    };

(2)HashTable1

HashTable中的模板参数,与RBTree的模板参数类似,都是Key与Value。不过值得关注的是,哈希结构存储时,需要上层传入"哈希函数"。毕竟只有整数才能取模,如果上层的key是字符串类型呢??我们, 又该如何做?

哈希函数:

我们如何处理字符串类型的哈希函数结构?

但是如果子串字母顺序不一样,则映射到同一个值(这个也叫做"哈希冲突")。

关于字符串的哈希函数,有很多前辈们都提供了一定的方法函数。

我们就随便借一个来试试(更多的字符串哈希函数)。

    template<class K>
    struct HashFunc
    {
        size_t operator()(const K& key)
        {
            return key;
        }
    };
    
    //模板特化
    template<>
    struct  HashFunc<std::string>
    {
        size_t operator()(const std::string& str)
        {
            size_t hash = 0;
            for (auto& e : str)
            {
                hash *= 131;
                hash += e;
            }
            return hash;
        }
    };

毕竟字符串等不是整数的类型是作为Key时的少数,大多数都是直接可以去key作为整数的取模。

我们就可以简单地搭一下HashTable的框架。

    template<class Value,class Key,class KeyOfValue,class Hash>
    class HashTable
    {
    public:
        typedef HashTable_node<Value> Node;

    private: 
        std::vector<Node*> _tables;  //哈希桶
        size_t n = 0; //有效个数
    }; 

(3)迭代器

同样,我们来看看源码。

opeartor++;

这样,我们也就完成了一个简单的迭代器。

    template<class Value, class Key, class KeyOfValue, class Hash>
    class HashTable;

        template<class Key,class Value,class KeyOfVal,class Hash>
    struct __HashIterator__
    {
        typedef HashTable_node<Value> Node;
        typedef HashTable<Key, Value, KeyOfVal, Hash> HT;
        HT* _ht;
        Node* _node;

        KeyOfVal kot;
        Hash hf;

        typedef __HashIterator__<Key, Value, KeyOfVal, Hash> Self;
        __HashIterator__(Node* node,HT* ht)
            :_node(node),
            _ht(ht)
        {}

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

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

        bool operator!=(const Self& s)const
        {
            return _node != s._node;
        }
    
        bool operator==(const Self& s)const
        {
            return _node == s._node;
        }

        //前向迭代器
        Self& operator++() 
        {
            if (_node->_next)
            {
                _node = _node->_next;
            }
            else
            {
                size_t hashi = hf(kot(_node->_data)) % _ht->_tables.size();
                hashi++;

                while (hashi < _ht->_tables.size())
                {
                    if (_ht->_tables[hashi])
                    {
                        _node = _ht->_tables[hashi];
                        break;
                    }
                    else
                    {
                        hashi++;
                    }
                }
                //遍历完成
                if (hashi == _ht->_tables.size())
                    _node = nullptr;
            }
            return *this;
        }
    };

取自《STL源码剖析》


(4)HashTable2

迭代器:

我们给原HashTable添入迭代器。

typedef __HashIterator__<Value, Key, KeyOfValue, Hash> iterator;
        iterator begin()
        {
            //返回第一个非空的HashData
            for (size_t i = 0;i < _tables.size();++i)
            {
                if (_tables[i])
                {
                    return iterator(_tables[i], this);
                }
            }
            return iterator(nullptr, this);
        }

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

Erase\find:

        iterator Find(const Key& key)
        {
            if (_n == 0) return end();
            //Key下标    
            size_t hashi = Hash()(key) % _tables.size();
            Node* cur = _tables[hashi];
            while (cur)
            {
                if (KeyOfValue()(cur->_data) == key)
                {
                    return iterator(cur, this);
                }
                else
                {
                    cur = cur->_next;
                }
            }
            
            return end();
        }

        bool Erase(const Key& key)
        {
            size_t hashi = Hash()(KeyOfValue()(key)) % _tables.size();
            Node* cur = _tables[hashi];
            Node* prev = nullptr;
            while (cur)
            {
                if (KeyOfValue()(cur->_data) == key)
                {
                    //1.头删
                    if (cur == _tables[hashi])
                    {
                        _tables[hashi] = cur->_next;
                    }
                    else
                    {
                        //2.中间删
                        prev->_next = cur->_next;
                    }
                    delete cur;
                    --_n;
                    return true;
                }
                else
                {
                    //迭代
                    prev = cur;
                    cur = cur->_next;
                }
            }
            return false;
        }

这些功能逻辑简单,实现起来不是很复杂。不解释。

Insert:

面对扩容问题:

STL提供了一份素数表,用来开辟table的空间大小。

源码库:

        inline unsigned long __stl_next_prime(unsigned long n)
        {
            static const int __stl_num_primes = 28;
            static const unsigned long __stl_prime_list[__stl_num_primes] =
            {
                53, 97, 193, 389, 769,
                1543, 3079, 6151, 12289, 24593,
                49157, 98317, 196613, 393241, 786433,
                1572869, 3145739, 6291469, 12582917, 25165843,
                50331653, 100663319, 201326611, 402653189, 805306457,
                1610612741, 3221225473, 4294967291
            };
        
            for (int i = 0; i < __stl_num_primes; ++i)
            {
                if (__stl_prime_list[i] > n)
                {
                    //返回 > n 的那next数组大小
                    return __stl_prime_list[i];
                }
            }
            
            return __stl_prime_list[__stl_num_primes - 1];
        }

扩容:

            //扩容
            if (_n == _tables.size())
            {
                std::vector<Node*> newtable;
                newtable.resize(__stl_next_prime(_tables.size(),nullptr);

                //计算节点并拷贝
                for (int i = 0;i < _tables.size();++i)
                {
                    Node* cur = _tables[i];
                    //处理一个桶里的 链表
                    while (cur)
                    {
                        Node* next = cur->_next;
                        //重新计算hashi
                        size_t hashi = Hash()(KeyOfValue()(cur->_data)) % newtable.size();
                        //头插
                        cur->_next = newtable[hashi];
                        newtable[hashi] = cur;

                        cur = next;
                    }
                    _tables[i] = nullptr;
                }

                //交换
                _tables.swap(newtable);
            }

插入:

        std::pair<iterator, bool> Insert(const Value& data)
        {
            iterator it = Find(KeyOfValue()(data));
            if (it != end())
            {
                return std::make_pair(it, false);
            }
            
            ///..扩容

            size_t hashi = Hash()(KeyOfValue()(data)) % _tables.size();
            Node* newnode = new Node(data);
            //头插
            newnode->_next = _tables[hashi];
            _tables[hashi] = newnode;
            ++_n;
            return std::make_pair(iterator(newnode,this), true);
        }    

四、unordered_map\unordered_set封装

当把unordered_map\unordered_set底层的架子搭好后,我们现在可以来对它们进行封装了。

unordered_map;

    template<class K,class V,class Hash = HashFunc<K>>
    class unordered_map
    {
        struct MapOfKey
        {
            const K& operator()(const std::pair<K, V>& kv)
            {
                return kv.first;
            }
        };
    public:
        typedef typename dy_OpenHash::HashTable<K, std::pair<const K, V>, MapOfKey, Hash>::iterator iterator;

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

        std::pair<iterator, bool> insert(const std::pair<K, V>& data)
        {
            return _ht.Insert(data);
        }

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

    private:
        dy_OpenHash::HashTable<K, std::pair<const K,V>, MapOfKey, Hash> _ht;
    };

unordered_set;

    template<class K,class Hash = HashFunc<K>>
    class unordered_set
    {
        struct SetOfKey
        {
            const K& operator()(const K& key)
            {
                return key;
            }
        };
    public:
    typedef typename dy_OpenHash::HashTable<K, K, SetOfKey, Hash>::iterator iterator;

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

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

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

    private:
        dy_OpenHash::HashTable<K, K, SetOfKey, Hash> _ht;
    };

这里也不多解释,和Mapset差不多。

测试:

那么它们封装后的测试也能行得通。差不多最后的"工程"也就结束了。


最后留了一个小问题:

为什么unordered_map\unordered_set const迭代器没有复用普通迭代器?

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/380636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业对不同形态CRM系统价格需求不同

很多企业在选型时关心CRM客户管理系统的价格&#xff0c;有人对CRM的价格完全没有概念&#xff0c;也有的人先问价格再看其他。CRM价格在系统选型中到底有多重要&#xff1f;不同类型CRM系统的价格是否有所不同&#xff1f; CRM的不同产品形态也会影响价格 通常情况下&#x…

十五、MyBatis使用PageHelper

1.limit分页 limit分页原理 mysql的limit后面两个数字&#xff1a; 第一个数字&#xff1a;startIndex&#xff08;起始下标。下标从0开始。&#xff09; 第二个数字&#xff1a;pageSize&#xff08;每页显示的记录条数&#xff09; 假设已知页码pageNum&#xff0c;还有每页…

移动端笔记

目录 一、移动端基础 二、视口 三、二倍图/多倍图 四、移动端开发 &#xff08;一&#xff09;开发选择 &#xff08;二&#xff09;常见布局 &#xff08;三&#xff09;移动端技术解决方案 五、移动WEB开发之flex布局 六、移动WEB开发之rem适配布局 #END&#xff08…

嘀嗒出行再闯IPO:千军万马我无懈

羽扇纶巾笑谈间&#xff0c;千军万马我无懈。 在激烈竞争中再度冲刺港交所IPO的嘀嗒出行&#xff0c;闪露出一丝歌词里的气魄。交通运输部下属网约车监管信息交互系统的数据显示&#xff0c;截至2023年1月31日&#xff0c;全国共有300家网约车平台公司取得网约车平台经营许可。…

如何使用COM-Hunter检测持久化COM劫持漏洞

关于COM-Hunter COM- Hunter是一款针对持久化COM劫持漏洞的安全检测工具&#xff0c;该工具基于C#语言开发&#xff0c;可以帮助广大研究人员通过持久化COM劫持技术来检测目标应用程序的安全性。 关于COM劫持 微软在Windows 3.11中引入了(Component Object Model, COM)&…

2月第4周榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年2月20日-2月26日飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B…

基频估计算法简介

基频估计算法 F0 estimate methods 估计F0的方法可以分为三类:基于时域、基于频域、或混合方法。本文详细介绍了这些方法。 所有的算法都包含如下三个主要步骤&#xff1a; 1.预处理&#xff1a;滤波&#xff0c;加窗分帧等 2.搜寻&#xff1a;可能的基频值F0&#xff08;候选…

chatgpt到底颠覆了什么 第二部分

以第二个理由就是两个字&#xff0c;垄断。 现在谈到范式转变&#xff0c;如果首先谈的还是算法&#xff0c;那说明还没有透彻理解范式改变范式改变&#xff0c;首先要改的是什么。是什么&#xff1f;是参赛资格。 过去我相信大企业大团队聚拢了许多聪明的脑袋&#xff0c;但我…

基于神经网络补偿的主动悬架自适应控制

目录 前言 1. 1/4悬架模型 2.仿真分析 2.1仿真模型 2.2仿真结果 2.1 形① 2.2 形② 3. 总结 前言 上两篇博客我们介绍了神经网络补偿控制律的仿真测试&#xff0c;从仿真结果我们可以得知神经网络具有逼近扰动&#xff0c;并将其补偿的作用。 上两篇文章链接&#xf…

在nestjs中进行typeorm cli迁移(migration)的配置

在nestjs中进行typeorm cli迁移(migration)的配置 在学习nestjs过程中发现typeorm的迁移配置十分麻烦,似乎许多方法都是旧版本的配置&#xff0c;无法直接使用. 花了挺长时间总算解决了这个配置问题. db.config.ts 先创建db.config.ts, 该文件export了两个对象&#xff0c;其…

AcWing算法提高课-3.1.2信使

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 题目传送门点这里 题目描述 战争时期&#xff0c;前线有 nnn 个哨所&#xff0c;每个哨所可能会与其他若干个哨所之间有通信联系。 信使负责在哨所之间传递信息&#xff0c;当然&#xff0c;…

CPU 偏高,和linux常用命令

CPU 偏高&#xff0c;和linux常用命令一、1、常用命令2、top 查看整体的cpu占有率&#xff08;哪个进程cpu占用过高&#xff09;3、top -Hp 8779 查看该pid 下哪个进程占用过高4、打印dump日志 重点关注&#xff08;RUNNABLE、BLOCKED&#xff09;一、 1、常用命令 整机 top …

k8s ConfigMap 中 subPath 字段和 items 字段

Kubernetes中什么是subPath 有时&#xff0c;在单个 Pod 中共享卷以供多方使用是很有用的。volumeMounts.subPath 属性可用于指定所引用的卷内的子路径&#xff0c;而不是其根路径。 这句话理解了&#xff0c;基本就懂subPath怎么用了&#xff0c;比如我们要替换nginx.cnf, 挂…

map和set的使用

文章目录关联式容器树形结构的关联式容器setinsert增减erase删除multiset修改mappair<key,value>insertoperator[] 的引入insert和operator[]的区别multimap小结map的使用统计最喜欢吃的前几种水果前K个高频单词&#xff0c;返回单词的频率由高到低,频率相同时&#xff0…

Isaac-gym(9):项目更新、benchmarks框架梳理

一、项目更新 近期重新git clone isaac gym的强化部分&#xff08;具体见系列第5篇&#xff09;时发现官方的github库有跟新&#xff0c;git clone下来后发现多了若干个task&#xff0c;在环境配置上也有一定区别。 例如新旧两版工程项目的setup.py区别如下&#xff1a; git …

现在的00后太强了,几个问题差点给我问懵了

前言 我们公司刚入职一个00后小伙&#xff0c;今天在办公室交流了一下&#xff0c;他问我会不会自动化测试&#xff0c;我说懂一点&#xff0c;然后直接问了我几个自动化测试问题&#xff0c;差点直接给我问懵了&#xff01; 问题如下&#xff1a; 我们在制定自动化测试实施…

计算机组成原理4小时速成5:输入输出系统,io设备与cpu的链接方式,控制方式,io设备,io接口,并行串行总线

计算机组成原理4小时速成5&#xff1a;输入输出系统&#xff0c;io设备与cpu的链接方式&#xff0c;控制方式&#xff0c;io设备&#xff0c;io接口&#xff0c;并行串行总线 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c…

刷题笔记1 | 704. 二分查找,27. 移除元素

704. 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 输入: nums [-1,0,3,5,9,12], target 9 输出: 4 …

备战蓝桥杯——sort函数

备战蓝桥杯——sort函数排列字母lambda匿名函数排列字母 链接: 排列字母 不用多说&#xff0c;很简单的签到题&#xff0c;我们先来了解一下sort函数的用法 list.sort(cmpNone, keyNone, reverseFalse) cmp:进行比较的方法&#xff08;可以自定义排序的方法&#xff0c;通常…

vue2前端实现html导出pdf功能

1. 功能实现方案 1.html转换成canvas后生成图片导出pdf&#xff08;本文选用&#xff09; html转canvas插件&#xff1a;html2canvas是一款将HTML代码转换成Canvas的插件&#xff1b;canvas生成pdf&#xff1a;jsPDF是一个使用Javascript语言生成PDF的开源库 2.HTML代码转出…