unordered_map的实现-hashmap

本文详细介绍了C++ STL中unordered_map的实现原理,它基于hashmap,元素无序,通常具有O(1)的读写复杂度。unordered_map通过初始化连续存储的桶数组来工作,使用hash函数确定元素位置。当冲突发生时,采用链表或红黑树存储冲突节点。此外,文章还讨论了unordered_map的初始化、扩容策略、插入、删除和查找操作的细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++STL中map包含两大类

  • map:采取红黑树方式,map中的元素有序,读写都是O(LogN)的时间复杂度
  • unordered_map:元素无序,采取hashmap的实现方式,即连续存放的桶,数组+链表方式,一般的认为读写都是O(1)复杂度

 

unordered_map是典型的采取空间换时间的方式达到一个良好的性能,

  • 初始化一定大小的vector(连续存储的数组)vector中index位置下标元素为一个bucket(桶)
  • 针对一个(key,value)根据hash函数(常见的如 取模%)定位到所在的index位置,
  • 然后在bucket的中进行查找或者插入,具体实现为当元素个数较少时,采取List方式,较多时采取红黑树存储冲突的节点

 

构造函数:

   HashMap(int size = 4, double loadFactor = 0.75) : mLoadFactor(loadFactor), mBucketNum(0)
    {
        mHashMap.resize(size);//调整为size初始化大小
    }

 

首先是hashmap的空间问题,一种较优的做法是其空间大小为2的倍数,而根据Key  采取以下的方式定位

index=(len-1)&key   其中len为hashmap的大小  即vector的大小 总共桶的数量

如4大小的空间   key=5    那么按位与运算如下: 101&11 =》 01 即存放在index=1位置


    int idx(int key)
    {
       
        return (mHashMap.size() - 1)& key;

    }

 

 另外一点是扩容问题,当已经使用的桶的数量超过总数量的一定比例时,按照一定的倍数(常见的设定为2)扩大桶的总数

称为扩容因子: loadFactor   其不能过小,否则造成过早扩容,也不能过大,造成扩容不及时。

一般设定大小为0.75                               因此 load=use_bucketnum/hashmap.size()  > loadFactor ?  resize:do_nothing

由于总的桶的数目增加两倍,原先index位置的元素可能在新的空间中仍然是index 也可能是 index+原先桶的总数

(将原先存在内容的桶分散到 两部分(旧的部分、新的部分))

 

    void expand()
    {
       
        vector < list<Pair<K,V>>> oldHashMap;
        mHashMap.swap(oldHashMap);//将原先的hashmap先转移出
        mHashMap.resize(oldHashMap.size()*2);
        for (int i = 0; i < oldHashMap.size(); i++)
        {
            list<Pair<K, V>>& bucketList = oldHashMap[i];
            if (bucketList.size() > 0)  //将原先那些不为空的桶  重新hash一遍找到合适位置放入
            {
                int index = idx(bucketList.begin()->first);
                mHashMap[index] = bucketList;

            }
           
        }

    }

 

 

插入:

首先判断是否需要扩容。根据key 定位到所在的index  然后首先判断所在的list中是否包含该key  当不包含时,将(key,value)插入到末尾 。注意点,若该list大小为0 即空bucket   那么已经使用的桶数量++   (采取mBucketNum记录)

 

  void insert(const Pair<K,V> &pair)
    {
        double load = mBucketNum * 1.0 / mHashMap.size();//判断当前的负载
        if (load > mLoadFactor) //需要扩容
        {
            expand();
        }
        int index = idx(pair.first);//定位到所在的桶
     //   cout << "index: " << index << endl;
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
            mBucketNum++;//使用的桶数量增加
       }
        for (auto &i : bucketList)
        {
            if (i.first == pair.first)
            {
                i.second = pair.second;//更新k v
                return;  //已经包含

            }
     
        }
        bucketList.push_back(pair);

    }

 

删除:

首先定位到所在index  若为空直接返回,否则遍历list删除  采取iterator方式

注意点,若删除以后List大小为0 那么mBucketNum--

 

   void erase(const K& k) //删除k v
    {
        int index = idx(k);//找到所在桶
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
           return;
        }
        for (auto it=bucketList.begin();it!=bucketList.end();it++)
        {
            if (it->first ==k)
            {
                bucketList.erase(it);
                break;
            }

        }
        if (bucketList.size() == 0)
            mBucketNum--;  //删除后桶list为空


    }

 

 

查找:

返回所在 pair<k,v>*指针,并且重载一个[]运算符返回key对应的value,注意点在通过 for()循环时  采取 &引用方式

并且返回&item 所在项取地址

 //查找
    Pair<K, V>* find(const K& k)
    {

        int index = idx(k);//找到所在桶
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
            return nullptr;
        }
        for (auto &i : bucketList)   //这里采取引用方式的到 pair实体
        {
            if (i.first == k)
            {
               return &i;   //返回实际地址
           
            }

        }
        return nullptr;
    

    }
    V operator[](const K& k)  //重载[]运算符   
    {
        int index = idx(k);//找到所在桶
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
            mBucketNum++;
          
        }
        else
        {
            for (auto i : bucketList)
            {
                if (i.first == k)
                {
                    return i.second;

                }

            }

        }
        V v;
        this->insert(Pair<K,V>(k, v));
        return v;




    }

 

完整实现与测试:采取模板函数实现   

参数fun(const A& a)  采取引用参数可避免形参、实参深拷贝开销,同时添加const 避免内部修改

#include<iostream>
#include<vector>
#include<list>
#include<string>
#include<algorithm>
using namespace std;

template<typename K,typename V>
//pair 对
struct Pair
{

    Pair(const K& k=K(), const V& v=V()) :first(k), second(v) {}
    K first;
    V second;



};
template<typename K,typename V>
class HashMap
{
private:
    vector<list<Pair<K, V>>> mHashMap;//hash表
    double mLoadFactor;//加载因子
    int mBucketNum;//桶的使用数量



    //hash函数采取取模  具体做法为  (len-1)&key  位运算快速定位到下标


    int idx(int key)
    {
       
        return (mHashMap.size() - 1)& key;

    }

    void expand()
    {
       
        vector < list<Pair<K,V>>> oldHashMap;
        mHashMap.swap(oldHashMap);//将原先的hashmap先转移出
        mHashMap.resize(oldHashMap.size()*2);
        for (int i = 0; i < oldHashMap.size(); i++)
        {
            list<Pair<K, V>>& bucketList = oldHashMap[i];
            if (bucketList.size() > 0)  //将原先那些不为空的桶  重新hash一遍找到合适位置放入
            {
                int index = idx(bucketList.begin()->first);
                mHashMap[index] = bucketList;

            }
           
        }

    }


public:
    HashMap(int size = 4, double loadFactor = 0.75) : mLoadFactor(loadFactor), mBucketNum(0)
    {
        mHashMap.resize(size);//调整为size初始化大小
    }

    //插入key,value  首先根据key借助hash函数 找到对应的index
    //然后判断所在的桶是否包含(key)若不包含则在末尾插入

    void insert(const Pair<K,V> &pair)
    {
        double load = mBucketNum * 1.0 / mHashMap.size();//判断当前的负载
        if (load > mLoadFactor) //需要扩容
        {
            expand();
        }
        int index = idx(pair.first);//定位到所在的桶
     //   cout << "index: " << index << endl;
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
            mBucketNum++;//使用的桶数量增加
       }
        for (auto &i : bucketList)
        {
            if (i.first == pair.first)
            {
                i.second = pair.second;//更新k v
                return;  //已经包含

            }
     
        }
        bucketList.push_back(pair);

    }



    void erase(const K& k) //删除k v
    {
        int index = idx(k);//找到所在桶
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
           return;
        }
        for (auto it=bucketList.begin();it!=bucketList.end();it++)
        {
            if (it->first ==k)
            {
                bucketList.erase(it);
                break;
            }

        }
        if (bucketList.size() == 0)
            mBucketNum--;  //删除后桶list为空


    }
    //查找
    Pair<K, V>* find(const K& k)
    {

        int index = idx(k);//找到所在桶
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
            cout << "size=0" << endl;
            return nullptr;
        }
        for (auto &i : bucketList)   //这里采取引用方式的到 pair实体
        {
            if (i.first == k)
            {
               return &i;   //返回实际地址
           
            }

        }
        return nullptr;
    

    }
    V operator[](const K& k)  //重载[]运算符   
    {
        int index = idx(k);//找到所在桶
        list<Pair<K, V>>& bucketList = mHashMap[index];  //采用引用方式
        if (bucketList.size() == 0)
        {
            mBucketNum++;
          
        }
        else
        {
            for (auto i : bucketList)
            {
                if (i.first == k)
                {
                    return i.second;

                }

            }

        }
        V v;
        this->insert(Pair<K,V>(k, v));
        return v;




    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值