文章目录
- 📖 前言
- 1. 复用同一个哈希桶⚡
- 1.1 🌀修改后结点的定义
- 1.2 🌀两个容器各自模板参数类型:
- 2. 改造之后的哈希桶⛳
- 3. 哈希桶的迭代器🔥
- 3.1 💥哈希桶的begin()和 end()的定义
- 3.2 💥 operator* 和 operator->
- 3.3 💥 operator++
- 3.4 💥 operator== 和 operator!=
- 4. 封装unordered_map和unordered_set⭕
📖 前言
与学习红黑树和map、set的思路一样,我们在学unordered_map和unordered_set时,也是先学底层结构,在用模拟的底层结构来自己封装一下该容器,动手实践来让我们更好的学习和理解底层逻辑。
前情回顾:哈希桶 👉 传送门
这里用到的封装思路和封装map、set的思路相同,都是更高维度的泛型编程。
思路复习:封装map和set 👉 传送门
1. 复用同一个哈希桶⚡
如何复用同一个哈希桶,我们就需要对哈希桶进行改造,将哈希桶改造的更加泛型一点,既符合Key模型,也符合Key_Value模型。
1.1 🌀修改后结点的定义
所以我们这里还是和封装map和set
时一样,无论是Key
还是Key_Value
,都用一个类型T来接收,这里高维度的泛型哈希表中,实现还是用的是Kye_Value
模型,K是不能省略的,同样的查找和删除
要用。
1.2 🌀两个容器各自模板参数类型:
如何取到想要的数据:
- 我们给每个容器配一个仿函数
- 各传不同的仿函数,拿到想要的不同的数据
同时我们再给每个容器配一个哈希函数。
2. 改造之后的哈希桶⛳
//K --> 键值Key,T --> 数据
//unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
//unordered_set ->HashTable<K, K, SetKeyOfT> _ht;
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
template<class K, class T, class KeyOfT, class HashFunc>
friend class __HTIterator;
typedef HashNode<T> Node;
public:
typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;
iterator begin()
{
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return iterator(cur, this);
}
}
return end();
}
iterator end()
{
return iterator(nullptr, this);
}
~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;
}
}
size_t GetNextPrime(size_t prime)
{
const int PRIMECOUNT = 28;
static const size_t primeList[PRIMECOUNT] =
{
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
};
//获取比prime大那一个素数
size_t i = 0;
for (i = 0; i < PRIMECOUNT; i++)
{
if (primeList[i] > prime)
return primeList[i];
}
return primeList[i];
}
pair<iterator, bool> Insert(const T& data)
{
HashFunc hf;
KeyOfT kot;
iterator pos = Find(kot(data));
if (pos != end())
{
return make_pair(pos, false);
}
//负载因子 == 1 扩容 -- 平均每个桶挂一个结点
if (_tables.size() == _n)
{
//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
size_t newSize = GetNextPrime(_tables.size());
if (newSize != _tables.size())
{
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
//遍历旧表
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
//再对每个桶挨个遍历
while (cur)
{
Node* next = cur->_next;
size_t hashi = hf(kot(cur->_data)) % newSize;
//转移到新的表中
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
//将原表置空
_tables[i] = nullptr;
}
newTable.swap(_tables);
}
}
size_t hashi = hf(kot(data));
hashi %= _tables.size();
//头插到对应的桶即可
Node* newnode = new Node(data);
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
//有效数据加一
_n++;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K& key)
{
if (_tables.size() == 0)
{
return iterator(nullptr, this);
}
KeyOfT kot;
HashFunc hf;
size_t hashi = hf(key);
//size_t hashi = HashFunc()(key);
hashi %= _tables.size();
Node* cur = _tables[hashi];
//找到指定的桶之后,顺着单链表挨个找
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
cur = cur->_next;
}
//没找到返回空
return iterator(nullptr, this);
}
bool Erase(const K& key)
{
if (_tables.size() == 0)
{
return false;
}
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key);
hashi %= _tables.size();
//单链表删除结点
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
//头删
if (prev == nullptr)
{
_tables[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
private:
//指针数组
vector<Node*> _tables;
size_t _n = 0;
};
研究表明:除留余数法,最好模一个素数
- 通过查STL官方库我们也发现,其提供了一个取素数的函数
- 所以我们也提供了一个,直接拷贝过来
-
- 这样我们在扩容时就可以每次给素数个桶
-
- 在扩容时加了一条判断语句是为了防止素数值太大,过分扩容容易直接把空间(堆)干崩了
3. 哈希桶的迭代器🔥
3.1 💥哈希桶的begin()和 end()的定义
- 以第一个桶中第一个不为空的结点为整个哈希桶的开始结点
- 以空结点为哈希桶的结束结点
3.2 💥 operator* 和 operator->
同之前operator->的连续优化一样,不再赘述……
3.3 💥 operator++
备注:
- 这里要在哈希桶的类外面访问其私有成员
- 我们要搞一个友元类
- 迭代器类是哈希桶类的朋友
- 这样就可以访问了
思路:
- 判断一个桶中的数据是否遍历完
-
- 如果所在的桶没有遍历完,在该桶中返回下一个结点指针
-
- 如果所在的桶遍历完了,进入下一个桶
- 判断下一个桶是否为空
-
- 非空返回桶中第一个节点
-
- 空的话就遍历一个桶
后置++和之前一眼老套路,不赘述
注意:
- unordered_map和unordered_set是不支持反向迭代器的,从底层结构我们也能很好的理解(单链表找不了前驱)
- 所以不支持实现迭代器的operator- -
3.4 💥 operator== 和 operator!=
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;
//哈希桶的迭代器
template<class K, class T, class KeyOfT, class HashFunc>
class __HTIterator
{
typedef HashNode<T> Node;
typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
public:
Node* _node;
__HTIterator() {};
//编译器的原则是向上查找(定义必须在前面,否则必须先声明)
HashTable<K, T, KeyOfT, HashFunc>* _pht;
__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
:_node(node)
, _pht(pht)
{}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else//当前桶已经走完了,要走下一个桶
{
KeyOfT kot;
HashFunc hf;
size_t hashi = hf(kot(_node->_data)) % _pht->_tables.size();
hashi++;
//找下一个不为空的桶 -- 访问到了哈希表中私有的成员(友元)
for (; hashi < _pht->_tables.size(); hashi++)
{
if (_pht->_tables[hashi])
{
_node = _pht->_tables[hashi];
break;
}
}
//没有找到不为空的桶,用nullptr去做end标识
if (hashi == _pht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
bool operator==(const Self& s) const
{
return _node == s._node;
}
};
编译器的原则是向上查找(定义必须在前面,否则必须先声明)
4. 封装unordered_map和unordered_set⭕
有了上面的哈希桶的改装,我们这里的对map和set的封装就显得很得心应手了。
unordered_map的封装:
template<class K, class V, class HashFunc = DefaultHash<K>>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
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);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = insert(make_pair(key, V()));
return ret.first->second;
}
private:
Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;
};
这里unordered_map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。
对unordered_set的封装:
template<class K, class HashFunc = DefaultHash<K>>
class unordered_set
{
//SteKeyOfT是set专用的就用内部类
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename Bucket::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
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:
Bucket::HashTable<K, K, SetKeyOfT, HashFunc> _ht;
};