【C++】Unordered_map/Unordered_map使用哈希表封装

目录

 

1. 整体学习的思维导图

2. 链地址法哈希表

3. 封装实现

3.1 解决key/pair问题

3.2 解决取key/pair.first问题

3.3 解决类型转换的问题

3.4 实现迭代器Iterator/const_Iterator

3.4.1 分析迭代器的++

3.5 实现map的operator[] 

4. 整体实现

4.1 HashTable.h

4.2 Myunordered_map.h 

4.3 Myunordered_set.h 


 

1. 整体学习的思维导图

2. 链地址法哈希表

本次的封装实现基于上次实现完成的链地址法的哈希表,以下是生成实现的整体代码,具体分析参考哈希表那章。

namespace Hashbucket
{
        // 
        template<class K, class V>
        struct HashNode
        {
                pair<K, V> _kv;
                HashNode<K, V>* _next;
                
                HashNode(const pair<K, V>& kv)
                        :_kv(kv)
                        ,_next(nullptr)
                {}
        };

        // 哈希表对象
        template<class K, class V>
        class HashTable
        {
                typedef HashNode<K, V> Node;
        public:

                HashTable()
                        :_tables(11)
                        ,_n(0)
                {}
                
                ~HashTable()
                {
                    for (size_t i = 0; i < _tables.size(); i++)
                    {
                            Node* cur = _tables[i];
                            while (cur)
                            {
                                    Node* next = cur->_next;
                                    delete cur;
                                    cur = next;
                            }
                            _tables[i] = nullptr;
                    }
                }
                
                HashTable(const HashTable& HT)
                    :_tables(HT._tables.size())
                {
                    for (size_t i = 0; i < HT._tables.size(); i++)
                    {
                        Node* cur = HT._tables[i];
                        while (cur)
                        {
                            Insert(cur->_kv);
                            cur = cur->_next;
                        }
                    }
                }
                
                HashTable& operator=(const HashTable& HT)
                {
                    if (this != &HT)
                    {
                        // 释放旧数据
                        for (size_t i = 0; i < _tables.size(); i++)
                        {
                            Node* cur = _tables[i];
                            while (cur)
                            {
                                Node* next = cur->_next;
                                delete cur;
                                cur = next;
                            }
                            _tables[i] = nullptr;
                        }
                
                        // 插入新数据
                        for (size_t i = 0; i < HT._tables.size(); i++)
                        {
                            Node* cur = HT._tables[i];
                            while (cur)
                            {
                                Insert(cur->_kv);
                                cur = cur->_next;
                            }
                        }
                    }
                    return *this;
                }
                
                bool Insert(const pair<K, V>& kv)
                {                
                        if (Find(kv.first))
                                return false;
                        // 扩容
                        if (_n == _tables.size())
                        {
                                // 复用法
                                // 会创建新的节点+使用旧的节点
                                /*HashTable<K, V> newHT;
                                newHT._tables.resize(_tables.size() * 2);
                                for (size_t i = 0; i < _tables.size(); i++)
                                {
                                        Node* cur = _tables[i];
                                        while (cur)
                                        {
                                                newHT.Insert(cur->_kv);
                                                cur = cur->_next;
                                        }
                                }
                                _tables.swap(newHT._tables);*/

                                // 摘取法
                                // 使用旧的节点
                                vector<Node*> newtables(_tables.size() * 2);
                                for (size_t i = 0; i < _tables.size(); i++)
                                {
                                        Node* cur = _tables[i];
                                        while (cur)
                                        {
                                                Node* next = cur->_next;
                                                // 重新计算出映射位置hashi
                                                size_t hashi = kv.first % newtables.size();
                                                cur->_next = newtables[hashi];
                                                newtables[hashi] = cur;
                                                cur = next;
                                        }
                                        _tables[i] = nullptr;
                                }
                                _tables.swap(newtables);
                        }

                        // 计算出映射位置hashi,这个计算必须在扩容下面不然映射会出错
                        size_t hashi = kv.first % _tables.size();
                        // 创建一个新的节点
                        Node* newnode = new Node(kv);
                        // 头插法
                        newnode->_next = _tables[hashi];  // 新节点指向原来的节点
                        _tables[hashi] = newnode;                 // 新节点成为新的头结点
                        ++_n;                                                        // ++节点数
                        return true;
                }

                Node* Find(const K& key)
                {
                        // 算出hashi
                        size_t hashi = key % _tables.size();
                        Node* cur = _tables[hashi];
                        while (cur)
                        {
                                if (cur->_kv.first == key)
                                        return cur;
                                cur = cur->_next;
                        }
                        return nullptr;
                }

                bool Erase(const K& key)
                {
                        // 算出hashi
                        size_t hashi = key % _tables.size();
                        Node* prev = nullptr;
                        Node* cur = _tables[hashi];
                        while (cur)
                        {
                                if (cur->_kv.first == key)
                                {
                                        if (prev == nullptr)
                                        {
                                                _tables[hashi] = cur->_next;
                                        }
                                        else
                                        {
                                                prev->_next = cur->_next;
                                        }
                                        delete cur;
                                        --_n;
                                        return true;
                                }
                                prev = cur;
                                cur = cur->_next;
                        }
                        return false;
                }

        private:
                vector<Node*> _tables;        // 指针数组
                size_t _n = 0;                        // 记录个数
        };

}

3. 封装实现

3.1 解决key/pair问题

        由于unordered_set和unordered_map所需要的参数分别为key和pair,所以我们要让底层的哈希表同时支持两者,我们要将模版参数中的数据类型参数改为T。

template<class T>
struct HashNode
{
        T _data;
        HashNode<T>* _next;

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

template<class K, class T>
class HashTable
{
        // .......
        typedef HashNode<T> Node;
        bool Insert(const T& data);
        Node* Find(const K& key);
        bool Erase(const K& key);
        // ......
 };

3.2 解决取key/pair.first问题

        由于我们需要使用key来计算哈希表的映射位置,但是unordered_set/unordered_map使用的数据类型不同,需要根据不同情况取出key,我们需要再传一个仿函数GetOfKey来解决这个问题。

template<class K, class V>
class Myunordered_map
{
public:
        // 仿函数用于获取key
        struct getofkey
        {
                const K& operator()(const pair<K, V>& kv)
                {
                        return kv.first;
                }
        };

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

private:
        HashTable<K, pair<K, V>, getofkey> _tb;
};
template<class K>
class Myunordered_set
{
public:
        // 仿函数用于获取key
        struct getofkey
        {
                const K& operator()(const K& key)
                {
                        return key;
                }
        };

        bool Insert(const K& key)
        {
                return _tb.Insert(key);
        }

private:
        HashTable<K, K, getofkey> _tb;
};

3.3 解决类型转换的问题

        对于以上的哈希表存储正整数没有问题,但是遇到负数,字符串等等需要进行特殊处理才可以映射,因此我们需要再传入一个参数用于转换类型为正整数ChangeOfType。该模版参数应用于取模计算hashi时。

vector<string> v = { "Hello", "string", "left", "right" };
ouyang::Myunordered_set<string> st;
for (const auto& e : v)
{
        st.Insert(e);
}

template<class K>
struct changeoftype
{
        size_t operator()(const K& key)
        {
                return (size_t)key;
        }
};

// 模版特化string
template<>
struct changeoftype<string>
{
        size_t operator()(const string& s)
        {
                size_t key = 0;
                for (auto ch : s)
                {
                        key += ch;
                        key *= 131;
                }
                return key;
        }
};

把该模版参数传参位置设定在外壳部分->unordered_set/unordered_map,方便后续自定义类型设计仿函数进行类型转换。 

template<class K, class V, class ChangeOfType = changeoftype<K>>
class Myunordered_map
{
public:
// ....
};

template<class K, class ChangeOfType = changeoftype<K>>
class Myunordered_set
{
public:
// ....
};

// 哈希表对象
template<class K, class T, class GetOfKey, class ChangeOfType>
class HashTable
{
// ....
};

3.4 实现迭代器Iterator/const_Iterator

3.4.1 分析迭代器的++

 

  • 情况一:当前链表没有走到空,继续往下走

 

 

  • 情况二:当前链表走到了空,继续找寻下一个挂着链表的哈希映射位

    • 【1】找到哈希映射位继续重复情况一

    • 【2】找到尾巴_tables.size()都没有挂着链表的哈希映射位结束

这个过程中我们需要当前的节点_node和整个哈希表的大小。

template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
struct HashIterator
{
        typedef HashNode<T> Node;
        typedef HashIterator<K, T, Ref, Ptr, GetOfKey, changeoftype> Self;

        Node* _node;
        const HashTable<K, T, GetOfKey, changeoftype>* _HT;
        HashIterator(Node* node, const HashTable<K, T, GetOfKey, changeoftype>* HT)
                :_node(node)
                , _HT(HT)
        {}

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

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

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

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

        Self& operator++()
        {
                if (_node->_next)
                {
                        // 如果当前桶链还有节点继续往下走
                       _node->_next;
                }
                else
                {
                        // 当前桶链没有节点了,找寻下一个存在的桶链
                        GetOfKey gok;
                        changeoftype cot;
                        size_t hashi = cot(gok(_node->_data)) % _HT->_tables.size();
                        ++hashi;

                        while (hashi < _HT->_tables.size())
                        {
                                if (_HT->_tables[hashi])
                                        break;
                                ++hashi;
                        }

                        // 不在有效范围内
                        if (hashi == _HT->_tables.size())
                        {
                                _node = nullptr;   // end()
                        }
                        else { // 如果哈希映射位在有效范围内
                                _node = _HT->_tables[hashi];
                        }
                }
                return *this;
        }

};

并且我们需要在HashTable处声明友元,HashIterator中访问到了HashTable中的私有成员_n和_tables。 

// 友元->HashTable和HashIterator都互相依赖对方,HashIterator会访问私有成员_n和_tables。
template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
friend struct HashIterator;

3.5 实现map的operator[] 

V& operator[](const K& key)
{
        pair<Iterator, bool> ret = _tb.Insert({ key, V() });
        return ret.first->second;
}

4. 整体实现

4.1 HashTable.h

#pragma once
#include <iostream>
#include <vector>
using namespace std;

// 用于获取素数
inline unsigned long __stl_next_prime(unsigned long n)
{
        // Note: assumes long is at least 32 bits.
        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
        };
        const unsigned long* first = __stl_prime_list;
        const unsigned long* last = __stl_prime_list + __stl_num_primes;
        const unsigned long* pos = lower_bound(first, last, n);
        return pos == last ? *(last - 1) : *pos;
}

// 类型转化
template<class K>
struct changeoftype
{
        size_t operator()(const K& key)
        {
                return (size_t)key;
        }
};
// 模版特化:string
template<>
struct changeoftype<string>
{
        size_t operator()(const string& s)
        {
                size_t key = 0;
                for (auto ch : s)
                {
                        key += ch;
                        key *= 131;
                }
                return key;
        }
};

// 哈希节点
template<class T>
struct HashNode
{
        T _data;
        HashNode<T>* _next;

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


// 哈希表对象
template<class K, class T, class GetOfKey, class ChangeOfType>
class HashTable
{
        // 友元->HashTable和HashIterator都互相依赖对方
        // HashIterator会通过_HT访问HashTable的私有成员_n和_tables。
        template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
        friend struct HashIterator;
        
        typedef HashNode<T> Node;
public:
        // Iterator和ConstIterator需要放在public,方便map和set使用
        typedef HashIterator<K, T, T&, T*, GetOfKey, ChangeOfType> Iterator;
        typedef HashIterator<K, T, const T&, const T*, GetOfKey, ChangeOfType> ConstIterator;

        Iterator Begin()
        {
                // 哈希表中数据为0时
                if (_n == 0)
                        return End();
                // 找寻哈希表中第一个不为空的映射位置
                for (size_t i = 0; i < _tables.size(); i++)
                {
                        Node* cur = _tables[i];
                        if (cur)
                        {
                                return Iterator(cur, this);
                        }
                }
                // 找不到返回End()
                return End();
        }

        Iterator End()
        {
                // 定义End为nullptr
                return Iterator(nullptr, this);
        }

        ConstIterator Begin() const
        {
                // 哈希表中数据为0时
                if (_n == 0)
                        return End();
                // 找寻哈希表中第一个不为空的映射位置
                for (size_t i = 0; i < _tables.size(); i++)
                {
                        Node* cur = _tables[i];
                        if (cur)
                        {
                                return Iterator(cur, this);
                        }
                }
                // 找不到返回End()
                return End();
        }

        ConstIterator End() const
        {
                // 定义End为nullptr
                return Iterator(nullptr, this);
        }

public:
        // 构造
        HashTable()
                :_tables(__stl_next_prime(0))
                , _n(0)
        {}

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

        // 拷贝构造
        HashTable(const HashTable& other)
                :_tables(other._tables.size()) // 申请和other一样的空间大小
        {
                // 获取key
                GetOfKey gok;
                // 转换类型
                ChangeOfType cot;
                for (size_t i = 0; i < other._tables.size(); i++)
                {
                        Node* cur = other._tables[i];
                        while (cur)
                        {
                                Insert(cur->_data);
                                cur = cur->_next;
                        }
                }
        }

        // 赋值运算符重载
        HashTable& operator=(const HashTable& other)
        {
                if (this != &other)
                {
                        // 先清理当前哈希表的资源
                        for (size_t i = 0; i < _tables.size(); i++)
                        {
                                Node* cur = _tables[i];
                                while (cur)
                                {
                                        Node* next = cur->_next;
                                        delete cur;
                                        cur = next;
                                }
                                _tables[i] = nullptr;
                        }                
                        _tables = vector<Node*>(__stl_next_prime(other._tables.size()));
                        GetOfKey gok;
                        ChangeOfType cot;
                        // 遍历源哈希表的每个桶
                        for (size_t i = 0; i < other._tables.size(); ++i)
                        {
                                Node* cur = other._tables[i];
                                while (cur)
                                {
                                        // 插入当前节点的数据到新哈希表
                                        Insert(cur->_data);
                                        cur = cur->_next;
                                }
                        }
                }
                return *this;
        }

        // 插入
        pair<Iterator, bool> Insert(const T& data)
        {
                // 获取key
                GetOfKey gok;
                // 转换类型
                ChangeOfType cot;
                // 先查找是否存在,不实现mult版本
                Iterator ret = Find(gok(data));
                if (ret != End())
                        return make_pair(ret, false);
                // 扩容
                if (_n == _tables.size())
                {
                        // 摘取法
                        // 使用旧的节点
                        vector<Node*> newtables(__stl_next_prime(_tables.size()) + 1);
                        for (size_t i = 0; i < _tables.size(); i++)
                        {
                                Node* cur = _tables[i];
                                while (cur)
                                {
                                        Node* next = cur->_next;
                                        // 重新计算出映射位置hashi
                                        size_t hashi = cot(gok(data)) % newtables.size();
                                        cur->_next = newtables[hashi];
                                        newtables[hashi] = cur;
                                        cur = next;
                                }
                                _tables[i] = nullptr;
                        }
                        _tables.swap(newtables);
                }

                // 计算出映射位置hashi,这个计算必须在扩容下面不然映射会出错
                size_t hashi = cot(gok(data)) % _tables.size();
                // 创建一个新的节点
                Node* newnode = new Node(data);
                // 头插法
                newnode->_next = _tables[hashi];  // 新节点指向原来的节点
                _tables[hashi] = newnode;                 // 新节点成为新的头结点
                ++_n;                                                        // ++节点数
                return { Iterator(newnode, this), true };
        }

        // 查找
        Iterator Find(const K& key)
        {
                GetOfKey gok;
                ChangeOfType cot;
                // 算出hashi
                size_t hashi = cot(key) % _tables.size();
                Node* cur = _tables[hashi];
                while (cur)
                {
                        if (gok(cur->_data) == key)
                                return Iterator(cur, this);
                        cur = cur->_next;
                }
                return End();
        }

        // 删除
        bool Erase(const K& key)
        {
                GetOfKey gok;
                ChangeOfType cot;
                // 算出hashi
                size_t hashi = cot(key) % _tables.size();
                Node* prev = nullptr;
                Node* cur = _tables[hashi];
                while (cur)
                {
                        if (gok(cur->_data) == key)
                        {
                                if (prev == nullptr)
                                {
                                        _tables[hashi] = cur->_next;
                                }
                                else
                                {
                                        prev->_next = cur->_next;
                                }
                                delete cur;
                                --_n;
                                return true;
                        }
                        prev = cur;
                        cur = cur->_next;
                }
                return false;
        }

private:
        vector<Node*> _tables;        // 指针数组
        size_t _n = 0;                        // 记录个数
};

// Hash迭代器
template<class K, class T, class Ref, class Ptr, class GetOfKey, class changeoftype>
struct HashIterator
{
        typedef HashNode<T> Node;
        typedef HashIterator<K, T, Ref, Ptr, GetOfKey, changeoftype> Self;

        Node* _node;
        const HashTable<K, T, GetOfKey, changeoftype>* _HT;
        HashIterator(Node* node, const HashTable<K, T, GetOfKey, changeoftype>* HT)
                :_node(node)
                , _HT(HT)
        {}

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

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

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

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

        Self& operator++()
        {
                if (_node->_next)
                {
                        // 如果当前桶链还有节点返回
                        _node = _node->_next;
                }
                else
                {
                        // 当前桶链没有节点了,找寻下一个存在的桶链
                        GetOfKey gok;
                        changeoftype cot;
                        size_t hashi = cot(gok(_node->_data)) % _HT->_tables.size();
                        ++hashi;

                        while (hashi < _HT->_tables.size())
                        {
                                if (_HT->_tables[hashi])
                                        break;
                                ++hashi;
                        }

                        // 不在有效范围内
                        if (hashi == _HT->_tables.size())
                        {
                                _node = nullptr;   // end()
                        }
                        else { // 如果哈希映射位在有效范围内
                                _node = _HT->_tables[hashi];
                        }
                }
                return *this;
        }

};

4.2 Myunordered_map.h 

#pragma once
#include "HashTable.h"

namespace ouyang
{
        template<class K, class V, class ChangeOfType = changeoftype<K>>
        class Myunordered_map
        {
                // getodkey需要放在typedef前面,因为是模版参数之一
                // 仿函数用于获取key
                struct getofkey
                {
                        const K& operator()(const pair<K, V>& kv)
                        {
                                return kv.first;
                        }
                };
        public:
                // typename 事先声明存在该对象,等模版实例化再去寻找。
                typedef typename HashTable<K, pair<const K, V>, getofkey, ChangeOfType>::Iterator iterator;
                typedef typename HashTable<K, pair<const K, V>, getofkey, ChangeOfType>::ConstIterator const_iterator;
                pair<iterator, bool> Insert(const pair<K, V>& kv)
                {
                        return _tb.Insert(kv);
                }

                iterator Find(const K& key)
                {
                        return _tb.Find(key);
                }

                bool Erase(const K& key)
                {
                        return _tb.Erase(key);
                }

                iterator begin()
                {
                        return _tb.Begin();
                }

                iterator end()
                {
                        return _tb.End();
                }

                const_iterator begin() const
                {
                        return _tb.Begin();
                }

                const_iterator end() const
                {
                        return _tb.End();
                }

                V& operator[](const K& key)
                {
                        pair<iterator, bool> ret = _tb.Insert({ key, V() });
                        return ret.first->second;
                }
        private:
                HashTable<K, pair<const K, V>, getofkey, ChangeOfType> _tb;
        };
}

4.3 Myunordered_set.h 

#pragma once
#include "HashTable.h"

namespace ouyang
{
        template<class K, class ChangeOfType = changeoftype<K>>
        class Myunordered_set
        {
                // getodkey需要放在typedef前面,因为是模版参数之一
                // 仿函数用于获取key
                struct getofkey
                {
                        const K& operator()(const K& key)
                        {
                                return key;
                        }
                };
        public:
                // typename 事先声明存在该对象,等模版实例化再去寻找。
                typedef typename HashTable<K, const K, getofkey, ChangeOfType>::Iterator iterator;
                typedef typename HashTable<K, const K, getofkey, ChangeOfType>::ConstIterator const_iterator;
                pair<iterator, bool> Insert(const K& key)
                {
                        return _tb.Insert(key);
                }

                iterator Find(const K& key)
                {
                        return _tb.Find(key);
                }

                bool Erase(const K& key)
                {
                        return _tb.Erase(key);
                }

                iterator begin()
                {
                        return _tb.Begin();
                }

                iterator end()
                {
                        return _tb.End();
                }

                const_iterator begin() const
                {
                        return _tb.Begin();
                }

                const_iterator end() const
                {
                        return _tb.End();
                }

        private:
                HashTable<K, const K, getofkey, ChangeOfType> _tb;
        };
}

 

 

哈希表unordered_map是相关概念,但在具体实现和使用上存在一些区别。 哈希表是一种数据结构,用于实现键值对的存储和查找。它通过哈希函数将键映射到一个固定大小的数组索引位置,然后将值存储在对应索引的位置。哈希表可以用于解决快速查找的问题,但需要处理哈希冲突的情况。 unordered_mapC++标准库中的一个容器,它是基于哈希表实现的。unordered_map提供了一种方便的方式来存储和检索键值对,具有快速的插入、删除和查找操作。它可以存储具有不同类型的键和值,并且支持自定义的哈希函数。 unordered_map哈希表之间的区别在于: 1. 实现方式:unordered_mapC++标准库中的容器,提供了封装好的接口和功能。而哈希表是一种数据结构的概念,需要自己实现或使用第三方库来构建。 2. 接口和功能:unordered_map提供了丰富的成员函数和操作符重载来访问、修改和遍历容器中的元素。而哈希表需要自己实现这些功能。 3. 键类型:unordered_map可以存储不同类型的键和值,只要满足一定的要求。而哈希表的键类型通常是限定的,需要根据具体实现来确定。 4. 哈希冲突处理:unordered_map采用开放地址法(例如线性探测)或链地址法来解决哈希冲突。而哈希表的冲突处理方法可以根据具体实现来选择。 总的来说,unordered_mapC++使用哈希表实现的一种容器,提供了方便的接口和功能。哈希表是一种数据结构的概念,需要自己实现或使用第三方库来构建。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值