每一个不曾起舞的日子都是对生命的辜负
哈希封装unordered系列
- 前言
- 一.封装的迭代器
- 二.改良后的HashTable.h
- 三.封装的UnorderedSet.h
- 四.封装的UnorderedMap.h
- 五.Test.cpp及测试结果
前言
unordered_map、unordered_set与map、set的区别是unoedered系列无序,除此之外功能上没有区别。但二者之间底层不同,前者底层为哈希,后者为红黑树。之前所学到的红黑树封装map和set时,为了map和set能够共用一套红黑树结构,我们将红黑树的参数类型以及模板数量类型进行的改良,增加一个能够读取参数相应大小的仿函数KeyOfT
,对于这次的封装,同样采用这种方式。上一篇中实现了两种方法:开放地址法、哈希桶,由于源码采用哈希桶并且哈希桶的方式能够更好的解决冲突问题,因此再次以哈希桶为基准进行改良,进而封装unordered_set和unordered_map。
此外,对于哈希实现unordered_map来说,有operator[]的重载,因此对于Insert的返回值应该改为pair<K, V>类型,这样才能实现上述重载。
有封装必然有迭代器,但对于哈希系列,并没有反向迭代器,同时通过观察源码,t发现这次的const迭代器并没有复用非const迭代器:
一.封装的迭代器
对于封装的迭代器,主要就是operator++的实现,对于++,一定是先在一个桶内向下迭代,如果碰到空,那就需要转移到另一个桶的头结点,当然了,如果剩下的头结点都为空,那么迭代器最终就走到了end(),也变成了空。需要注意的是,在迭代器的封装中,利用了HashTable,需要前置声明,因为在封装的迭代器中需要有如下HT* ht
这样类型的成员变量,只有通过这个变量才能在operator++时找到下一个桶。在映射时我们发现,也会用到_tables.size();
,这是HashTable中私有成员的成员函数,为了可以访问,有两种方式,其一是在HashTable中再封装一个公有函数返回这个值,其二是在HashTable中写出迭代器的友元,下面用到的就是友元的方式访问。
//前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;
//为什么const迭代器没有复用??
template<class K, class T, class Hash, class KeyOfT>
struct __HTIterator
{
typedef HashNode<T> Node;
typedef __HTIterator<K, T, Hash, KeyOfT> Self;
typedef HashTable<K, T, Hash, KeyOfT> HT;
Node* _node;
HT* _ht;
__HTIterator(Node* node, HT* ht)
:_node(node)
, _ht(ht)
{}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else//当前桶走完了,要找下一个桶的第一个
{
KeyOfT kot;
Hash hash;
size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
++hashi;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
else
{
++hashi;
}
}
//后面没有桶了
if (hashi == _ht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
};
除了普通的迭代器,同样的也存在const属性的迭代器,但通过观察源码发现,const迭代器并不像以前的vector、list、红黑树迭代器一样和普通迭代器通过相同模板演化,而是独立的写了一个const迭代器,这实际上是因为在vector<Data*>
中, 如果使用const版本,那么_tables使用[]返回的就是const版本,那么Node*
就相当于const Node*
,就会导致权限放大,无法构造;如果改成如下的const HT* _ht,const Node* _node,又会导致[]不能进行修改。C++/STL源码3.0/stl_hashtable.h · 每天都要进步呀/CPP - 码云 - 开源中国 (gitee.com)
二.改良后的HashTable.h
我们在原有基础上,将Hash的缺省去掉了,因为新增了一个转换的仿函数KeyofT,为了更加灵活,在对应封装的Unordered系列中对Hash进行了调用,这样会更加的灵活。
#pragma once
//仿函数:解决s映射问题,完全没有关联的类型不能随便转,这个不能string转整形,因此还需要写一个
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
//特化
template<>
struct HashFunc<string>
{
size_t operator()(const string& key)
{
//BKDR-Hash
size_t hash = 0;
for (auto& ch : key)
{
hash *= 131;
hash += ch;
}
return hash;//这样映射不易产生哈希冲突
}
};
namespace buckethash
{
template<class T>
struct HashNode
{
//pair<K, V> _kv;
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
//前置声明
template<class K, class T, class Hash, class KeyOfT>
class HashTable;
//为什么const迭代器没有复用???答:
template<class K, class T, class Hash, class KeyOfT>
struct __HTIterator
{
typedef HashNode<T> Node;
typedef __HTIterator<K, T, Hash, KeyOfT> Self;
typedef HashTable<K, T, Hash, KeyOfT> HT;
Node* _node;
HT* _ht;
__HTIterator(Node* node, HT* ht)
:_node(node)
, _ht(ht)
{}
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else//当前桶走完了,要找下一个桶的第一个
{
KeyOfT kot;
Hash hash;
size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
++hashi;
while (hashi < _ht->_tables.size())
{
if (_ht->_tables[hashi])
{
_node = _ht->_tables[hashi];
break;
}
else
{
++hashi;
}
}
//后面没有桶了
if (hashi == _ht->_tables.size())
{
_node = nullptr;
}
}
return *this;
}
};
template<class K, class T, class Hash, class KeyOfT>
class HashTable
{
typedef HashNode<T> Node;
public:
typedef __HTIterator<K, T, Hash, KeyOfT> iterator;
template<class K, class T, class Hash, class KeyOfT>
friend struct __HTIterator;//
iterator begin()
{
for (size_t i = 0; i < _tables.size(); i++)
{
if (_tables[i])
{
return iterator(_tables[i], this);
}
}
return iterator(nullptr, this);
}
iterator end()
{
return iterator(nullptr, this);
}
HashTable()
:_n(0)
{
//_tables.resize(10);
_tables.resize(__stl_next_prime(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;
}
}
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;//仿函数:取数据
iterator it = Find(kot(data));
if (it != end())
return make_pair(it, false);
// 负载因子控制在1,超过就扩容
if (_tables.size() == _n)
{
HashTable<K, T, Hash, KeyOfT> newHT;
/*newHT._tables.resize(_tables.size() * 2);
for (auto cur : _tables)
{
while (cur)
{
newHT.Insert(cur->_kv);
cur = cur->_next;
}
}
_tables.swap(newHT._tables);*/
vector<Node*> newTables;
//newTables.resize(2 * _tables.size(), nullptr);
newTables.resize(__stl_next_prime(_tables.size()), nullptr);
for (size_t i = 0; i < _tables.size(); ++i)
{
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
size_t hashi = Hash()(kot(data)) % newTables.size();
//头插到新表
cur->_next = newTables[hashi];
newTables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
}
size_t hashi = Hash()(kot(data)) % _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)
{
KeyOfT kot;
size_t hashi = Hash()(key) % _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
else
{
cur = cur->_next;
}
}
return end();
}
bool Erase(const K& key)
{
size_t hashi = Hash()(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;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}
inline unsigned long __stl_next_prime(unsigned long n)
{
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
};
for (int i = 0; i < __stl_num_primes; ++i)
{
if (__stl_prime_list[i] > n)
{
return __stl_prime_list[i];
}
}
return __stl_prime_list[__stl_num_primes - 1];
}
private:
vector<Node*> _tables; //指针数组
size_t _n = 0;
};
}
三.封装的UnorderedSet.h
#include"HashTable.h"
namespace cfy
{
template<class K, class Hash = HashFunc<K>>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename buckethash::HashTable<K, K, Hash, SetKeyOfT>::iterator iterator;
pair<iterator, bool> insert(const K& key)
{
return _ht.Insert(key);
}
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
private:
buckethash::HashTable<K, K, Hash, SetKeyOfT> _ht;
};
void test_unordered_set()
{
unordered_set<int> us;
us.insert(13);
us.insert(3);
us.insert(23);
us.insert(5);
us.insert(5);
us.insert(6);
us.insert(15);
us.insert(223342);
us.insert(6);
unordered_set<int>::iterator it = us.begin();
while (it != us.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : us)
{
cout << e << " ";
}
cout << endl;
}
}
四.封装的UnorderedMap.h
#include"HashTable.h"
namespace cfy
{
template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename buckethash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT>::iterator iterator;
pair<iterator, bool> insert(const pair<K, V>& data)
{
return _ht.Insert(data);
}
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
private:
buckethash::HashTable<K, pair<const K, V>, Hash, MapKeyOfT> _ht;
};
void test_unordered_map()
{
string arr[] = { "苹果", "西瓜", "香蕉", "苹果", "西瓜" , "苹果", "苹果",
"西瓜","苹果","香蕉", "苹果","香蕉" };
unordered_map<string, int> countMap;
for (auto& e : arr)
{
countMap[e]++;
}
for (const auto& kv : countMap)
{
cout << kv.first << ":" << kv.second << endl;
}
}
}
五.Test.cpp及测试结果
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<set>
using namespace std;
#include"HashTable.h"
#include"UnorderedSet.h"
#include"UnorderedMap.h"
int main()
{
cfy::test_unordered_set();
cfy::test_unordered_map();
return 0;
}