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