C++起始之路——用哈希表封装myunordered_set和myunordered_map

💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

《数据结构与算法》《算法》《C++起始之路》


目录

1.源码及框架分析

2.模拟实现


1.源码及框架分析

SGI-STL30版本源代码中没有unordered_map和unordered_set,SGI-STL30版本是C++11之前的STL版本,这两个容器是C++11之后才更新的。但是SGI-STL30实现了哈希表,只是容器的名字是hash_map和hash_set,它是作为非标准容器出现的,非标准是指非C++标准规定必须实现的,源代码在hash_map/hash_set/set_hash_map/stl_hash_set/stl_hashtable.h中

hash_map和hash_set的实现结构框架核心部分截取:

cpp 复制代码
//stl_hash_set
template<class Value,class HashFcn=hash<Value>,
            class EqualKey=equal_to<Value>,
            class Alloc=alloc>
class hash_set{
private:
    typedef hashtable<Value,Value,HashFcn,identity<Value>,
                                EqualKey,Alloc> ht;
    ht rep;
public:
    typedef typename ht::key_type key_type;
    typedef typename ht::value_type value_type;
    typedef typename ht::hasher hasher;
    typedef typename ht::key_equal key_equal;
    
    typedef typename ht::const_iterator iterator;
    typedef typename ht::const_iterator const_iterator;

    hasher hash_funct() const {return rep.hash_funct();}
    key_equal key_eq() const {return rep.key_eq();}
};

//stl_hash_map
template<class Key,class T,class HashFcn=hash<Key>,
                    class EqualKey=equal_to<Key>,
                    class Alloc=alloc>
class hash_map{
private:
    typedef hashtable<pair<const Key,T>,Key,HashFcn,
                        selectlst<pair<const Key,T>>,EqualKey,Alloc> ht;
    ht rep;
public:
    typedef typename ht::key_type key_type;
    typedef T data_type;
    typedef T mapped_type;
    typedef typename ht::value_type value_type;
    typedef typename ht::hasher hasher;
    typedef typename ht::key_equal key_equal;

    typedef typename ht::iterator iterator;
    typedef typename ht::const_iterator const_iterator;
};

//stl_hashtable.h
template<class Value,class Key,class HashFcn,
            class ExtractKey,class EqualKey,
            class Alloc>
class hashtable{
public:
    typedef Key key_type;
    typedef Value value_type;
    typedef HashFcn hasher;
    typedef EqualKey key_equal;
private:
    hasher hash;
    key_equal equals;
    ExtractKey get_key;
    typedef __hashtable_node<Value> node;

    vector<node*,Alloc> buckets;
    size_type num_elements;
public:
    typedef __hashtable_iterator<Value,Key,HashFcn,ExtractKey,EqualKey,
                Alloc> itertor;
    pair<iterator,bool> insert_unique(const value_type& obj);
    const_iterator find(const key_type& key) const;
};

template<class Value>
struct __hashtable_node{
    __hahstable_node* next;
    Value val;
};

●通过源码可以看到,结构上hash_map和hash_set跟map和set的完全类似,复用同一个hashtable实现key和key/value结构,hash_set传给hash_ table的是两个key,hash_map传给hash_table的是pair<const key,value>

●需要注意源码里面跟map/set源码类似,命名风格比较乱,这里比map和set还乱,hash_set模板参数居然用的Value命名,hash_map用的是Key和T命名。

2.模拟实现

2.1实现出复用哈希表的框架,并支持insert

●unordered_map和unordered_set复用之前实现的哈希表。

●key参数用K,value参数用V,哈希表中数据类型,使用T。

●其次跟map和set相比而言unordered_map和unordered_set的模拟实现类结构更复杂一点,但是大框架和思路完全类似。一i那位HashTable实现了泛型不知道T参数到底是K,还是pair<K,V>,那么insert内部进行插入时要用K对象转换成整型取模和K比较相等,因为pair的value不参与计数取模,且默认支持的key和value一起比较相等,需要时的任何时候只需要比较K对象,所以在unordered_map和unordered_set层分别实现一个MapKeyOfT和SetKeyOfT的仿函数传给HashTable的KeyOfT,然后HashTable中通过KEyOfT仿函数取出T类型对象中的K对象,再转换成整型取模和K比较相等。

cpp 复制代码
namespace Achieve{
    template<class K,class Hash=HashFunc<K>>
    class unordered_set{
        struct SetKeyOfT{
            const K& operator()(const K& kv){
                return key;
            }
        };
    public:
        bool insert(const K& key){
            return _ht.Insert(key);
        }
    private:
        hash_buckte::HashTable<K,K,SetKeyOfT,Hash> _ht;
    };
}
namespace Achieve{
    template<class K,class V,class Hash=HashFunc<K>>
    class unordered_map{
        struct MapKeyOfT{
            const K& operator()(const pair<K,V>& kv){
                return kv.first;
            }
        };
    public:
        bool insert(const pair<K,V>& kv){
            return _ht.Insert(kv);
        }
    private:
        hash_buckte::HashTable<K,pair<K,V>,MapKeyOfT,Hash> _ht;
    };
}
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;
}
namespace hash_buckte{
    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{
        typedef HashNode<T> Node;
    public:
        
        HashTable()
            :_tables(__stl_next_prime(0))
            ,_n(0)
        {}
        ~HashTable(){
            for(int i=0;i<_tables.size();i++){
                Node* cur=_tables[i];
                while(cur){
                    Node* next=cur->_next;
                    delete cur;
                    cur=next;
                }
                _tables[i]=nullptr;
            }
        }
        bool Insert(const T& data){
            KeyOfT kot;
            Hash hash;
            Iterator it=Find(kot(data));
            if(it!=End()) return false;
            //当负载因子为1时,扩容
            if(_n==_tables.size()){

                //直接复用Insert,不好
                /*HashTable<K,V> newht;
                newht._tables.resize(_tables.size()*2);
                for(int i=0;i<_tables.size();i++){
                    Node* cur=_tables[i];
                    while(cur){
                        newht.Insert(cur);
                        cur=cur->_next;
                    }
                }
                _tables.swap(newht);*/

                vector<Node*> newTable(__stl_next_prime(_tables.size()+1));
                //newht._tables.resize(_tables.size()*2);
                //newht._tables.resize(__stl_next_prime(_tables.size()+1));
                for(int i=0;i<_tables.size();i++){
                    Node* cur=_tables[i];
                    while(cur){
                        Node* next=cur->_next;
                        size_t hash1=hash(kot(cur->_data))%newTable.size();
                        //头插
                        cur->_next=newTable[hash1];
                        newTable[hash1]=cur;
                        cur=next;
                    }
                    _tables[i]=nullptr;
                }
                _tables.swap(newTable);
            }
            //头插
            size_t hash1=hash(kot(data))%_tables.size();
            Node* newNode=new Node(data);
            newNode->_next=_tables[hash1];
            _tables[hash1]=newNode;
            ++_n;
            return true;
        }
    private:
        vector<Node*> _tables;
        size_t _n;
    };
}

2.2支持iterator的实现

iterator核心源代码

cpp 复制代码
template<class Value,class Key,class HashFcn,
            class ExtractKey,class EqualKey,class Alloc>
struct __hashtable_iterator{
    typedef hashtable<Value,Key,HashFcn,ExtractKey,EqualKey,Alloc>
            hashtable;
    typedef __hashtable_iterator<Value,Key,HashFcn,
                                ExtractKey,EqualKey,Alloc>
            iterator;
    typedef __hashtable_const_iterator<Value,Key,HashFcn,ExtraceKey,EqualKey,Alloc>
            const_iterator;
    typedef __hashtable_node<Value> node;
    
    typedef forward_iterator_tag iterator_vategory;
    typedef Value value_type;

    node* cur;
    hashtable* ht;
        
    __hashtable_iterator(node* n,hashtable* tab): cur(n),ht(tab) {}
    __hashtable_iterator() {}
    reference operator*() const { return cur->val;}
#ifndef __SGI_STL_NO_ARROW_OPERATOR
    pointer operator->() const {return &(operator*());}
#endif /*__SGI_STL_NO_ARROW_OPERATOR*/
    iterator& operator++();
    iterator operator++(int);
    bool operator==(const iterator& it) const {return cur==it.cur;}
    bool operator!=(const iterator& it) const {return cur!=it.cur;}

template<class V,class K,class HF,class ExK,class EqK,class A>
__hashtable_iterator<V,K,HF,ExK,EqK,A>&
__hashtable_iterator<V,K,HF,ExK,EqK,A>::operator++(){
    const node* old=cur;
    cur=cur->next;
    if(!cur){
        size_type bucket=ht->bkt_num(old->val);
        while(!curL&&++bucket<ht->buckets.size())
            cur=ht->buckets[bucket];
    }
    return *this;
}

iterator实现思路分析

●iterator实现的大框架跟list的iterator思路是一致的,用一个类型封装节点的指针,再通过重载运算符实现,迭代器像指针一样访问的行为,要注意哈希表的迭代器是单向迭代器。

●难点是operator++的实现。iterator中有一个指向节点的指针,若当前桶下面还有节点,则节点的指针指向下一个节点即可。若当前桶走完了,则需要想办法计算找到下一个桶。这里的难点反而是结构设计的问题,上面的源码,我们可以看到iterator中除了有节点的指针,还有哈希桶对象的指针,这样当前桶走完了,要计算下一个桶就相对容易多了,用key值计算出当前桶位置,依次往后找下一个不为空的桶即可。

●begin()返回第一个桶中的第一个节点指针构造的迭代器,这里end()返回迭代器可以用空表示。

●unoedered_set的iterator也不支持修改,把unordered_set的第二个模板参数改成const K即可,HashTable<k,const K,SetKeyOfT,Hash> _ht;

​​​​​​​●unordered_map的iterator不支持修改key但是可以修改value,把unordered_map的第二个模板参数pair的第一个参数改成const K即可,HashTable<K,pair<const K,V>,MapKeyOfT,Hash> _ht;

​​​​​​​

2.3map支持[]

​​​​​​​●unordered_map要支持[]主要需要修改insert返回值支持,修改HashTable中的insert返回值为pair<Iterator,bool> Insert(const T& data)

​​​​​​​2.4Achieve::unordered_map和Achieve::unordered_set代码实现

cpp 复制代码
namespace Achieve{
    template<class K,class Hash=HashFunc<K>>
    class unordered_set{
        struct KeyOfT{
            const K& operator()(const K& key){
                return key;
            }
        };
    public:
        typedef typename hash_buckte::HashTable<K,const K,KeyOfT,Hash>::Iterator iterator;
        typedef typename hash_buckte::HashTable<K,const K,KeyOfT,Hash>::ConstIterator const_iterator;
        iterator begin(){
            return _ht.Begin();
        }
        iterator end(){
            return _ht.End();
        }
        const_iterator begin() const{
            return _ht.Begin();
        }
        const_iterator end() const{
            return _ht.End();
        }
        pair<iterator,bool> insert(const K& key){
            return _ht.Insert(key);
        }
        iterator Find(const K& key){
            return _ht.Find(key);
        }
        bool erase(const K& key){
            return _ht.Erase(key);
        }
    private:
        hash_buckte::HashTable<K,const K,KeyOfT,Hash> _ht;
    };
            void print(const unordered_set<int>& s){
            unordered_set<int>::const_iterator it=s.begin();
            while(it!=s.end()){
                cout<<*it<<" ";
                ++it;
            }
            cout<<endl;

            for(auto e:s){
                cout<<e<<' ';
            }
            cout<<endl;
        }
        void test_set(){
            int a[]={3,11,86,88,1,881,5,6,7,6};
            unordered_set<int> s;
            for(auto e:a){
                s.insert(e);
            }
            unordered_set<int>::iterator it=s.begin();
            while(it!=s.end()){
                cout<<*it<<" ";
                ++it;
            }
            cout<<endl;

            for(auto e:s){
                cout<<e<<' ';
            }
            cout<<endl;
            print(s);
        }
}

namespace Achieve{
    template<class K,class V,class Hash=HashFunc<K>>
    class unordered_map{
        struct MapKeyOfT{
            const K& operator()(const pair<K,V>& kv){
                return kv.first;
            }
        };
    public:
        typedef typename hash_buckte::HashTable<K,pair<const K,V>,MapKeyOfT,Hash>::Iterator iterator;
        typedef typename hash_buckte::HashTable<K,pair<const K,V>,MapKeyOfT,Hash>::ConstIterator const_iterator;
        iterator begin(){
            return _ht.Begin();
        }
        iterator end(){
            return _ht.End();
        }
        const_iterator begin() const{
            return _ht.Begin();
        }
        const_iterator end() const{
            return _ht.End();
        }
        V& operator[](const K& key){
            pair<iterator,bool> ret=insert({key,V()});
            return ret.first->second;
        }
        pair<iterator,bool> insert(const pair<K,V>& kv){
            return _ht.Insert(kv);
        }
        iterator Find(const K& key){
            return _ht.Find(key);
        }
        bool erase(const K& key){
            return _ht.Erase(key);
        }
    private:
        hash_buckte::HashTable<K,pair<const K,V>,MapKeyOfT,Hash> _ht;
    };
    void test_map(){
        unordered_map<string,string> dict;
        dict.insert({"sort","排序"});
        dict.insert({"字符串","string"});

        dict.insert({"sort","排序"});
        dict.insert({"left","左边"});
        dict.insert({"right","右边"});

        dict["left"]="左边、剩余";
        dict["insert"]="插入";
        dict["string"];
        
        for(auto& kv:dict){
            cout<<kv.first<<":"<<kv.second<<endl;
        }
        cout<<endl;
        unordered_map<string,string>::iterator it=dict.begin();
        while(it!=dict.end()){
            it->second+='x';
            cout<<it->first<<":"<<it->second<<endl;
            ++it;
        }
        cout<<endl;
    }
}

template<class K>
struct HashFunc{
    size_t operator()(const K& key){
        return (size_t)key;
    }
};
//特化
template<>
struct HashFunc<string>{
    size_t operator()(const string& s){
        //BKDR
        size_t hash=0;
        for(auto ch:s){
            hash+=ch;
            hash*=131;
        }
        return hash;
    }
};

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;
}

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

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

    //前置声明,防止HashIterator不认识HashTable
    template<class K,class T,class KeyOfT,class Hash>
    class HashTable;

    template<class K,class T,class Ref,class Ptr,class KeyOfT,class Hash>
    struct HTIterator{
        typedef HashNode<T> Node;
        typedef HashTable<K,T,KeyOfT,Hash> HT;
        typedef HTIterator<K,T,Ref,Ptr,KeyOfT,Hash> Self;
        
        Node* _node;
        const HT* _ht;//取模时要用
    
        HTIterator(Node* node,const HT* ht)
            :_node(node)
            ,_ht(ht)
        {}
        Ref operator*(){
            return _node->_data;
        }
        Ptr operator->(){
            return &_node->_data;
        }
        bool operator!=(const Self& s){
            return _node!=s._node;
        }
        Self& operator++(){
            if(_node->_next){
                _node=_node->_next;
            }
            else{
                //找下一个不为空的桶
                Hash hash;
                KeyOfT kot;
                size_t hash0=hash(kot(_node->_data))%_ht->_tables.size();
                ++hash0;
                while(hash0<_ht->_tables.size()){
                    _node=_ht->_tables[hash0];                    
                    if(_node){
                        break;
                    }
                    else ++hash0;
                }
                //所有桶都走完了,end()给的空的标识的_node
                if(hash0==_ht->_tables.size()){
                    _node=nullptr;
                }                
            }
            return *this;
        }
    };
    template<class K,class T,class KeyOfT,class Hash>
    class HashTable{
        //友元声明,为防止模板参数冲突,将参数改名
        template<class K1,class T1,class Ref,class Ptr,class KeyOfT1,class Hash1>
        friend struct HTIterator;

        typedef HashNode<T> Node;
    public:
        typedef HTIterator<K,T,T&,T*,KeyOfT,Hash> Iterator;
        typedef HTIterator<K,T,const T&,const T*,KeyOfT,Hash> ConstIterator;
        
        HashTable()
            :_tables(__stl_next_prime(0))
            ,_n(0)
        {}
        ~HashTable(){
            for(int i=0;i<_tables.size();i++){
                Node* cur=_tables[i];
                while(cur){
                    Node* next=cur->_next;
                    delete cur;
                    cur=next;
                }
                _tables[i]=nullptr;
            }
        }
        Iterator Begin(){
            if(_n==0){
                return End();
            }
            for(int i=0;i<_tables.size();i++){
                Node* cur=_tables[i];
                if(cur) return Iterator(cur,this);;
            }
            return End();
        }
        Iterator End(){
            return Iterator(nullptr,this);
        }
        ConstIterator Begin() const{
            if(_n==0){
                return End();
            }
            for(int i=0;i<_tables.size();i++){
                Node* cur=_tables[i];
                if(cur) return ConstIterator(cur,this);;
            }
            return End();
        }
        ConstIterator End() const{
            return ConstIterator(nullptr,this);
        }
        pair<Iterator,bool> Insert(const T& data){
            KeyOfT kot;
            Hash hash;
            Iterator it=Find(kot(data));
            if(it!=End()) return {it,false};
            //当负载因子为1时,扩容
            if(_n==_tables.size()){

                //直接复用Insert,不好
                /*HashTable<K,V> newht;
                newht._tables.resize(_tables.size()*2);
                for(int i=0;i<_tables.size();i++){
                    Node* cur=_tables[i];
                    while(cur){
                        newht.Insert(cur);
                        cur=cur->_next;
                    }
                }
                _tables.swap(newht);*/

                vector<Node*> newTable(__stl_next_prime(_tables.size()+1));
                //newht._tables.resize(_tables.size()*2);
                //newht._tables.resize(__stl_next_prime(_tables.size()+1));
                for(int i=0;i<_tables.size();i++){
                    Node* cur=_tables[i];
                    while(cur){
                        Node* next=cur->_next;
                        size_t hash1=hash(kot(cur->_data))%newTable.size();
                        //头插
                        cur->_next=newTable[hash1];
                        newTable[hash1]=cur;
                        cur=next;
                    }
                    _tables[i]=nullptr;
                }
                _tables.swap(newTable);
            }
            //头插
            size_t hash1=hash(kot(data))%_tables.size();
            Node* newNode=new Node(data);
            newNode->_next=_tables[hash1];
            _tables[hash1]=newNode;
            ++_n;
            return {Iterator(newNode,this),true};
        }
        Iterator Find(const K& key){
            KeyOfT kot;
            Hash hash;
            size_t hash0=hash(key)%_tables.size();
            Node* cur=_tables[hash0];
            while(cur){
                if(kot(cur->_data)==key)
                    return Iterator(cur,this);
                cur=cur->_next;
            }
            return End();
        }
        bool Erase(const K& key){
            KeyOfT kot;
            Hash hash;
            //没有这个值
            if(!Find(key)) return false;
            size_t hash0=hash(key)%_tables.size();
            Node* cur=_tables[hash0];
            Node* prev=nullptr;
            while(cur){
                if(kot(cur->_data)==key){
                    //删除的节点是链表的头
                    if(!prev){
                        _tables[hash0]=cur->_next;
                    }
                    else{
                        prev->_next=cur->_next;
                    }
                    delete cur;
                    --_n;
                    return true;
                }
                else{
                    prev=cur;
                    cur=cur->_next;
                }
            }
            return false;
        }
    private:
        vector<Node*> _tables;
        size_t _n;
    };
}
相关推荐
Slow菜鸟1 小时前
Java 开发环境安装指南(7) | Nginx 安装
java·开发语言·nginx
沐苏瑶1 小时前
Java反序列化漏洞
java·开发语言·网络安全
心.c1 小时前
大厂高频手写题
开发语言·前端·javascript
guslegend2 小时前
AI生图第2节:python对接gpt-image-2模型API生图
开发语言·python·gpt
原来是猿2 小时前
Linux线程同步与互斥(四):日志系统与策略模式
linux·运维·开发语言·策略模式
进击的荆棘3 小时前
C++起始之路——哈希表的实现
数据结构·c++·散列表·哈希
卷心菜狗3 小时前
Python进阶--迭代器
开发语言·python
jr-create(•̀⌄•́)4 小时前
LeakyRelu链式法则
开发语言·python·深度学习
t***54410 小时前
如何配置Orwell Dev-C++使用Clang
开发语言·c++