【C++】哈希封装map与set

目录

前言:

一,底层哈希结构

1-1,迭代器的封装

1-2,哈希表的封装

二,unordered_map的封装

三,unordered_set的封装


前言:

        上一篇文章说明了哈希结构,这一篇文章来说明如何使用哈希结构来实现哈希容器unordered_map与unordered_set。这里的封装逻辑跟用红黑树封装map与set一样,map与set都只是套了一层膜,实际上是创建一颗红黑树,而这里的底层结构功能都是依靠哈希结构来实现,实际上是创建了一个哈希表。

        封装思想:跟map与set封装一样,map和set是建立红黑树结构,unordered_map与unordered_set是建立哈希表结构。无论是查找find、插入insert、删除erase,还是迭代器的实现,都是调用哈希结构。哈希结构这里采用拉链法实现。unordered_map与unordered_set向哈希结构中传递的模板参数我们来做以下分析。

        unordered_map与unordered_set的模板参数跟map与set一样,map中的V对应pair<const K, V>,set中的V对应const K,在向哈希结构传递模板参数时要传递K和对应的V。

        map与set后面的关键码都是K,上一篇的哈希结构在取关键码中我们都是直接从pair中取K,由于这里无法确定是map还是set,所以这里也需要传递跟map与set封装一样的转换K类型的伪函数Key。

        哈希结构的封装中哈希函数的关键码必须是正整数,因此这里还需传递将关键码转换成正整数的伪函数Con。

具体代码设计如下:

//哈希结构的头文件
#include "Unordered_HashTable.h" 
//unordered_set的结构封装
template <class K, class Con = Conversion<K>> //Con伪函数与上一篇哈希结构一样,将K转换成正整型
class unordered_set
{
    //伪函数—转换K类型
    struct key
    {
        const K& operator()(const K& data)
        {
            return data;
        }
    };
public:
    //.........
private:
    HashTable<K, const K, key, Con> _hashset;  //哈希表 
};


//哈希结构的头文件
#include "Unordered_HashTable.h"
//unordered_map的结构封装
template <class K, class V, class Con = Conversion<K>> //Con伪函数与上一篇哈希结构一样,将K转换成正整型
class unordered_map
{
    //伪函数—转换K类型
    struct key
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
public:
    //........
private:
    HashTable<K, pair<const K, V>, key, Con> _hashmap;  //哈希表
};


一,底层哈希结构

1-1,迭代器的封装

        迭代器的基本功能有*,->,++,--,==,!=。在设计迭代器时只要能满足这些功能即可。所有功能中主要是++和--的实现。不难发现,若直接在表中传递结点是可以实现的,只不过要在++和--中利用顺序表vector空间顺序存储的特点,运用二级指针来进行前后表空间的移动。这种方式的实现比较麻烦,因为这里我们还要知道表的边界。其实这里有更好的方法,我们直接向迭代器中传入表结构即可。但需注意传入这里需用哈希表指针,不能使用对象,防止浅拷贝析构函数释放两次空间。具体代码实现如下:

//转换
template<class K>
struct Conversion  //转换正整型
{
    size_t operator()(const K& data)
    {
        return data;
    }
};
template<>  //特殊处理串
struct Conversion<string>
{
    size_t operator()(const string& data)
    {
        size_t count = 0;
        for (auto e : data)
        {
            count += e;
            count *= 131;
        }
        return count;
    }
};


//结点: unordered_map中T是pair<const K, V>  unordered_set中T是const K
template<class T>
struct HashNode
{
    T _data;
    HashNode<T>* _next;    HashNode(const T& data = T())
        : _data(data)
        , _next(nullptr)
    {    }
};

//声明HashTable,因为封装的迭代器里面需要使用
template <class K, class T, class Key, class Con = Conversion<K>>
class HashTable;


//迭代器结构
template <class K, class T, class Key, class Con = Conversion<K>>
struct HashIterator
{
    typedef HashNode<T> Node;   //结点
    typedef HashTable<K, T, Key, Con> HT; //哈希表
    typedef HashIterator<K, T, Key, Con> Self;

    Node* _node;
    //HT _hashtable; 这里不能直接创建对象,因为下面迭代器构造函数的哈希表初始化是浅拷贝。表中结点都是动态开辟的空间,若创建对象,当开始释放迭代器空间时,里面哈希表中的析构函数会释放空间,原哈希表也会释放这块空间,导致释放了两次而出错,因此这里应使用指针,避免了浅拷贝发生析构函数释放动态空间两次的影响。
    //具体运用浅拷贝还是深拷贝,一定要思考动态开辟空间时析构函数释放空间的情况,若不存在这种情况可以进行浅拷贝
    HT* _hashtable; //_hashtable是类,当访问成员时可直接使用库中的->访问

    

    //哈希表是vector,vector底层空间都是连续的,这里可传入结点的指针,不传入哈希表指针,即vector内部空间的地址(指针的指针),当进行++或--时,直接跳转一小块空间。

    HashIterator(Node* node = nullptr, HT* hashtable = nullptr)
        :_node(node)
        , _hashtable(hashtable)
    {    }


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

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

    //++分两种情况: 要么此时结点不是桶的最底端结点,要么此时结点是桶的最底端结点
    Self& operator++() //前置++
    {
        assert(_node && _hashtable);
        //当此时结点不是桶的最底端结点时,直接返回桶的下一个结点位置
        if (_node->_next) _node = _node->_next;
        //当此时结点是桶的最底端结点时需要往表的后面遍历,直到找到表中第一个桶的头结点为止
        else
        {
            Key key;
            Con conversion;
            size_t pos = conversion(key(_node->_data)) % _hashtable->_table.size() + 1;
            while (pos < _hashtable->_table.size() && !_hashtable->_table[pos])    pos++;
            if (pos >= _hashtable->_table.size())
            {
                _node = nullptr;
                return *this;
            }
            _node = _hashtable->_table[pos];
        }
        return *this;
    }
    Self operator++(int) //后置++
    {
        assert(_node && _hashtable);
        //这里不存在动态开辟空间,因此浅拷贝即可
        Self s(*this);
        if (_node->_next) _node = _node->_next;
        else
        {
            Key key;
            Con conversion;
            size_t pos = conversion(key(_node->_data)) % _hashtable->_table.size() + 1;
            while (pos < _hashtable->_table.size() && !_hashtable->_table[pos]) pos++;
            if (pos >= _hashtable->_table.size())
            {
                _node = nullptr;
                return s;
            }
            _node = _hashtable->_table[pos];
        }
        return s;
    }
    //--分两种情况: 要么此时结点不是桶的头结点,要么此时结点是桶的头结点
    Self& operator--() //前置--
    {
        assert(_node && _hashtable);
        Key key;
        Con conversion;
        size_t pos = conversion(key(_node->_data)) % _hashtable->_table.size();
        //当此时结点不是桶的头结点时返回上一个结点        
        if (_hashtable->_table[pos] != _node)
        {
            Node* cur = _hashtable->_table[pos];
            while (cur->_next != _node) cur = cur->_next;
            _node = cur;
        }
        //当此时结点是桶的头结点时需要往前面遍历,直到找到表前面第一个桶的头结点
        else
        {
            pos--;
            while (pos >= 0 && !_hashtable->_table[pos]) pos--;
            if (pos < 0) assert(false);
            _node = _hashtable->_table[pos];
        }
        return *this;
    }
    Self operator--(int) //后置--
    {
        assert(_node && _hashtable);
        Self s(*this); //这里不存在动态开辟空间,因此浅拷贝即可
        Key key;
        Con conversion;
        size_t pos = conversion(key(_node->_data)) % _hashtable->_table.size();
        if (_hashtable->_table[pos] != _node)
        {
            Node* cur = _hashtable->_table[pos];
            while (cur->_next != _node) cur = cur->_next;
            _node = cur;
        }
        else
        {
            pos--;
            while (pos >= 0 && !_hashtable->_table[pos]) pos--;
            if (pos < 0) assert(false);
            _node = _hashtable->_table[pos];
        }
        return s;
    }

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

1-2,哈希表的封装

        哈希表封装unordered_map和unordered_set只需稍微变动插入、查找、删除功能和添加迭代器功能即可,具体代码和说明如下:

//声明中已有缺省类型,因为缺省类型使用了模板,所以为了避免缺省类型冲突系统不允许这里Con再次使用缺省类型
template <class K, class T, class Key, class Con>
class HashTable
{
    typedef HashNode<T> Node;
    //因为迭代器要访问哈希表,这里需声明友元。模板类型在声明友元后需加上模板类型
    friend struct HashIterator<K, T, Key, Con>;
public:
    typedef HashIterator<K, T, Key, Con> Iterator;

    HashTable(const size_t n = 10)
        : _count(0)
    {
        _table.resize(n, nullptr);
    }

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

    //封装begin和end(),用于赋值给迭代器
    Iterator begin()
    {
        for (size_t i = 0; i < _table.size(); i++)
        {
            if (_table[i])
            {
                //注意,由于迭代器构造函数参数有两个,这里无法直接返回。类这种情况可以使用以下创建匿名对象的方法进行转换。

                //当向迭代器时赋值时,是将当前对象的begin进行赋值,所以这里要传入此时对象的表指针,即this,以下同理。
                return Iterator(_table[i], this); //匿名对象实现隐式类型转换返回
            }
        }
        return Iterator(nullptr, this); //匿名对象实现隐式类型转换返回
    }
    Iterator end()
    {
        return Iterator(nullptr, this); //匿名对象实现隐式类型转换返回
    }

    Iterator Find(const K& data)
    {
        Con conversion;
        Key key;
        size_t pos = conversion(data) % _table.size();
        Node* cur = _table[pos];
        while (cur)
        {
            if (key(cur->_data) == data) return Iterator(cur, this);
            cur = cur->_next;
        }
        return end();
    }

    pair<Iterator, bool> Insert(const T& data)
    {
        Key key;
        Con conversion;
        if (Find(key(data)) != end()) return make_pair(Find(key(data)), false);
        //负载因子到1就扩容
        if (_count == _table.size())
        {
            //为保证效率这里不采用创建对象,直接创建表来重新搬运结点数据
            vector<Node*> newtable;
            newtable.resize(2 * _table.size(), nullptr);
            for (size_t i = 0; i < _table.size(); i++)
            {
                Node* cur = _table[i];
                while (cur)
                {
                    Node* next = cur->_next;
                    size_t pos = conversion(key(cur->_data)) % newtable.size();
                    cur->_next = newtable[pos];
                    newtable[pos] = cur;
                    cur = next;
                }
            }
            _table.swap(newtable);
        }
        size_t pos = conversion(key(data)) % _table.size();
        Node* node = new Node(data);
        node->_next = _table[pos];
        _table[pos] = node;
        _count++;
        return make_pair(Find(key(data)), true);
    }

    bool Erase(const K& data)
    {
        Con conversion;
        Key key;
        if (Find(data) == end()) return false;
        size_t pos = conversion(data) % _table.size();
        Node* cur = _table[pos];
        Node* prv = nullptr;
        while (key(cur->_data) != data)
        {
            prv = cur;
            cur = cur->_next;
        }
        if (cur == _table[pos]) _table[pos] = cur->_next;
        else prv->_next = cur->_next;
        delete cur;
        cur = nullptr;
        _count--;
        return true;
    }
private:
    vector<Node*> _table;
    size_t _count;
};


二,unordered_map的封装

        unordered_map的结构明白之后这里的封装就只是套了哈希结构的功能,即有关迭代器的begin、end()和功能接口的insert、find、erase,[],跟map封装一样。

#include "Unordered_HashTable.h"  //哈希结构头文件

template <class K, class V, class Con = Conversion<K>>
class unordered_map
{
    struct key
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
public:
    typedef typename Hash_bucket::HashTable<K, pair<const K, V>, key, Con>::Iterator iterator;

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

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

    pair<iterator, bool> insert(const pair<K, V>& kv)
    {
        return _hashmap.Insert(kv);
    }

    iterator find(const K& data)
    {
        return _hashmap.Find(data);
    }

    bool erase(const K& data)
    {
        return _hashmap.Erase(data);
    }

    V& operator[](const K& key)
    {
        _hashmap.Insert(make_pair(key, V()));
        iterator it = find(key);
        return it->second;
    }
private:
    HashTable<K, pair<const K, V>, key, Con> _hashmap;
};

样例测试:

void test_map1()
{
    unordered_map<string, string> dict;
    dict.insert(make_pair("sort", ""));
    dict.insert(make_pair("left", ""));
    dict.insert(make_pair("right", "L"));
    dict.insert(make_pair("string", ""));

    for (auto& kv : dict)
    {
        kv.second += 'y';

        cout << kv.first << ":" << kv.second << endl;
    }
    cout << endl;

    unordered_map<string, string>::iterator it = dict.begin();
    while (it != dict.end())
    {
        cout << it->first << ": " << it->second << endl;
        it++;
    }

    string n;
    cin >> n;
    if (dict.find(n) == dict.end())
    {
        cout << "No data" << endl;
    }
    else
    {
        it = dict.find(n);
        cout << "Exist data: " << it->first << ":" << it->second << endl;
    }

    dict.erase("string");
    dict.erase("left");
    cout << "After Erase string and left: ";
    for (auto& kv : dict) cout << kv.first << ":" << kv.second << "  ";
    cout << endl;
}


三,unordered_set的封装

        unordered_set与unordered_map同理,唯一要注意的是此容器没有重载"[]",且这里哈希重载迭代器->功能不能在unordered_set中运用,是为了在unordered_map中运用的,因为数据_data不是迭代器里面的数据。封装->的本质是左边是右边的地址,然后调用系统库中->访问类里面的数据,而unordered_map数据类型是pair,虽然迭代器中不存在数据,但C++pair库中专门封装了first和second访问数据,因此可以使用重载的->,封装代码如下:

#include "Unordered_HashTable.h"
template <class K, class Con = Conversion<K>>
class unordered_set
{
    struct key
    {
        const K& operator()(const K& data)
        {
            return data;
        }
    };
public:
    typedef typename Hash_bucket::HashTable<K, const K, key, Con>::Iterator iterator;

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

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

    pair<iterator, bool> insert(const K& data)
    {
        return _hashset.Insert(data);
    }

    iterator find(const K& data)
    {
        return _hashset.Find(data);
    }

    bool erase(const K& data)
    {
        return _hashset.Erase(data);
    }
private:
    HashTable<K, const K, key, Con> _hashset;
};

样例测试:

void test_set1()
{
    unordered_set<int> us;
    us.insert(3);
    us.insert(1);
    us.insert(7);
    us.insert(5);
    us.insert(15);
    us.insert(45);
    us.insert(7);

    unordered_set<int>::iterator it = us.begin();
    while (it != us.end())
    {
        //注意: 这里不能使用it->_data,因为_data不是迭代器里面的数据,封装->的本质是左边是右边的地址,然后调用系统库中->访问类里面的数据
        //cout << it->_data;错误使用
        cout << *it << " ";
        it++;
    }
    cout << endl;

    for (auto& e : us)
    {
        cout << e << " ";
    }
    cout << endl;

    it = us.begin()++;
    cout << "后置begin++: " << *it << endl;
    it = ++us.begin();
    cout << "前置begin++: " << *it << endl;
    cout << endl;

    for (int i = 0; i < 2; i++) it++;
    cout << "Iterator: " << *it << endl;
    unordered_set<int>::iterator its;
    its = --it;
    cout << "前置--: " << *it << endl;
    cout << endl;

    cout << "Iterator: " << *it << endl;
    its = it--;
    cout << "后置--: " << *its << endl;
    cout << endl;
}

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

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

相关文章

安装WSL2

PS C:\Users\pc> wsl --set-default-version 2 有关与 WSL 2 关键区别的信息&#xff0c;请访问 https://aka.ms/wsl2操作成功完成。PS C:\Users\pc> wsl --update 正在检查更新。 已安装最新版本的适用于 Linux 的 Windows 子系统。PS C:\Users\pc> wsl --shutdownPS…

PyTorch深度解析:Tensor——神经网络的核心构建块

在深度学习和神经网络的研究与应用中&#xff0c;Tensor&#xff08;张量&#xff09;无疑是一个核心概念。特别是在PyTorch这一强大的深度学习框架中&#xff0c;Tensor更是扮演了举足轻重的角色。本文将深入探讨PyTorch中的Tensor&#xff0c;从其基本定义、特性、操作到实际…

回溯算法练习day.3

39.组合总和 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目描述&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返…

uniapp Android 插件开发教程

一、下载uniapp提供的SDK Android 离线SDK - 正式版 | uni小程序SDK 二、在uniapp创建一个项目 查看包名&#xff1a;发行--> 原生app 云打包 三、进入dcloud官网 开发者中心 进入 应用管理 --> 我的应用 --> 点击应用名称-->各平台信息-->新增 这里需要这…

SQLite FTS3 和 FTS4 扩展(三十二)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;SQLite FTS5 扩展&#xff08;三十&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 概述 FTS3 和 FTS4 是 SQLite 虚拟表模块&#xff0c;允许用户执行 对一组文档进行全文搜索。最常见&#xff08;和最…

paddle.net怎么付款?paddle.net怎么订阅?

有需要的小伙伴可以使用Fomepay的卡进行订阅支付&#xff0c;我这里使用的是491090卡段&#xff0c;开卡步骤很简单&#xff0c;点击获取卡片 1、注册 2、填写姓名使用拼音或者英文名都可以 3、支付宝或者微信支付

基于51单片机的数字万用表设计

基于51单片机的数字万用表设计 &#xff08;仿真&#xff0b;程序&#xff0b;原理图PCB&#xff0b;设计报告&#xff09; 功能介绍 具体功能&#xff1a; 1.能够切换测量电压、电流、电阻&#xff1b; 2.数码管实时显示测量值&#xff1b; 3.短路报警&#xff1b; 4.测量…

Vue.extend()和我的两米大砍刀

Vue.extends是什么&#xff1f; 一个全局API,用于注册并挂载组件。 传统的引用组件的方式是使用import直接引入&#xff0c;但是使用Vue.extends()也可以实现。 使用规则 <div id"mount-point"></div>// 创建构造器 var Profile Vue.extend({templat…

Spring 声明式事务控制

1. 编程式事务控制相关对象 1.1 PlatformTransactionManager PlatformTransactionManager 接口是 spring 的事务管理器&#xff0c;它提供了我们常用的操作事务的方法。 PlatformTransactionManager 是接口类型&#xff0c;不同的 Dao 层技术则有不同的实现类。例如:Dao层技…

3分钟看懂Microchip 32位MCU CAN模块的配置

文章目录 CAN模块系统框图Microchip MCC Harmony下CAN模块配置选项CAN模块工作模式CAN模块中断模式CAN工作速率Bit Timing Calculation配置CAN 接收的配置CAN 发送的配置CAN 过滤器工作流程说明CAN 过滤器的配置 CAN模块系统框图 CAN的英文全称&#xff1a;Control Area Networ…

连续时间折线图的前后端实现

技术栈 vue3VChartegg.jsMySQL 需求 根据已有任务数据&#xff0c;获取连续天的任务完成的数量&#xff0c;并且通过接口返回后做成图表。预期数据如下&#xff1a; [{"x": "2024-01-01","y": 0},{"x": "2024-01-02",&q…

Python实现KDJ工具判断信号:股票技术分析的工具系列(8)

Python实现KDJ工具判断信号&#xff1a;股票技术分析的工具系列&#xff08;8&#xff09; 介绍算法公式 代码rolling函数介绍完整代码data代码KDJ.py 介绍 KDJ是一种技术指标&#xff0c;用于衡量价格动量&#xff0c;帮助交易者识别趋势的强度和转折点。 先看看官方介绍&am…

Vue入门篇:概念,快速入门,插值表达式,核心特性,基本Vue指令

目录 1.Vue是什么2.快速入门3.插值表达式{{}}1.作用:2.语法:3.插值表达式的注意点: 4.Vue响应式核心特性5.Vue指令 1.Vue是什么 Vue是一个流行的JavaScript框架&#xff0c;用于构建用户界面。它是一种用于构建单页面应用程序&#xff08;SPA&#xff09;的渐进式框架&#xff…

机器学习系统的设计

1.混淆矩阵 混淆矩阵作用就是看一看在测试集样本集中&#xff1a; 真实值是 正例 的样本中&#xff0c;被分类为 正例 的样本数量有多少&#xff0c;这部分样本叫做真正例&#xff08;TP&#xff0c;True Positive&#xff09;&#xff0c;预测为真&#xff0c;实际为真真实值…

NtripShare2024年第一季度主要技术进展

迷迷糊糊又是一个月没有写点什么&#xff0c;近期想清楚NtripShare在2024的要做什么事情&#xff0c;暂且将NtripShare要做的主要事情为搭建由软件与硬件之间的技术桥梁。 在过去的几年时间里NtripShare对硬件方面一直是规避的态度&#xff0c;今年开始要做一点软硬件搭界的技…

mmcv bug记录

图像分类任务要用到mmcv框架&#xff0c;记录遇到的问题 1. Can‘t import build_from_cfg from mmcv. 解决命令&#xff1a;pip install openmim && mim install mmcv-full 2. python分布式训练 解决方案&#xff1a; 租用多张A40卡&#xff0c;执行下述命令&…

跨站攻击CSRF实验

1.low等级 先利用Burp抓包 将get响应的url地址复制&#xff0c;发到网页上&#xff08;Low等级到这完成&#xff09; Medium&#xff1a; 再将抓到的包发到Repeater上,对请求中的Referer进行修改&#xff0c;修改成和url一样的地址&#xff0c;修改成功。 在这里修改后发送 然…

团队协作:如何利用 Gitee 实现多人合作项目的版本控制

文章目录 前言一、名词解释1、Git是什么&#xff1f;2、Gitee、GitHub和GitLab 二、操作步骤1.安装Git2.创建Gitee仓库3.用vscode连接仓库4. 克隆远程仓库 总结 前言 在软件开发中&#xff0c;有效地管理代码是至关重要的。Gitee 是一个功能强大的代码托管平台&#xff0c;提供…

使用clickhouse-backup迁移数据

作者&#xff1a;俊达 1 说明 上一篇文章中&#xff0c;我们介绍了clickhouse-backup工具。除了备份恢复&#xff0c;我们也可以使用该工具来迁移数据。 这篇文章中&#xff0c;我们提供一个使用clickhouse-backup做集群迁移的方案。 2 前置条件 1、源端和目标端网络联通&a…

LeetCode刷题实战5:最长回文子串

题目内容 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"aba"…