之前介绍了哈希表和解决哈希冲突的两种方式
今天介绍如何封装底层为哈希表的unordered_map/set,这里是以拉链法实现的哈希表作为底层结构构(哈希桶)
template
struct HashNode
{
//由于之后要用哈希桶封装unordered_map/set,因此这里可能存pair也可能存value
V _valuefiled;
HashNode* _next;//哈希桶结构是一个vector数组下挂着链表,定义一个指针指向下一个节点的位置
HashNode(const V& v)
:_valuefiled(v)
, _next(nullptr)
{}
};
bool Insert(const V& v)
{
//考虑增容
CheckCapacity();
KeyOfValue kov;
const K& key = kov(v);
//size_t index = key % _table.size();
size_t index = HashIndex(key, _table.size());
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_valuefiled) == key)
return false;
cur = cur->_next;
}
//走到这说明没有相同的元素,可以进行插入,
//由于哈希桶不规定产生冲突的序列有序,可以进行头插比较简单
Node* newnode = new Node(v);
newnode->_next = _table[index];
_table[index] = newnode;
++_size;
return true;
}
//前置声明,由于哈希表需要迭代器,迭代器又需要哈希表
template
class HashTable;
//实现哈希表的迭代器
template
struct HTIterator
{
typedef HashNode Node;
typedef HTIterator Self;
Node* _node;//节点指针
HashTable* _ht;//哈希表指针
HTIterator(Node* node, HashTable* ht)
:_node(node)
, _ht(ht)
{}
V& operator*()//返回节点的值
{
return _node->_valuefiled;
}
V* operator->()//返回节点指针
{
return &_node->_valuefiled;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
// 找下一个桶的第一个节点位置
//size_t i = KeyOfValue()(_node->_valuefiled) % _ht->_table.size();
size_t i = _ht->HashIndex(KeyOfValue()(_node->_valuefiled), _ht->_table.size());
++i;
for (; i < _ht->_table.size(); ++i)
{
if (_ht->_table[i] != nullptr)
{
_node = _ht->_table[i];
break;
}
}
if (i == _ht->_table.size())
{
_node = nullptr;
}
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
#include "HashTable.h"
#include "common.h"
template>
class UnorderedMap
{
struct MapKeyOfValue
{
const K& operator()(const pair& _kv)
{
return _kv.first;
}
};
public:
typedef typename HashTable, MapKeyOfValue, HashFunc>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair Insert(const pair& kv)
{
return _ht.Insert(kv);
}
V& operator[](const K& key)
{
std::pair ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
private:
HashTable, MapKeyOfValue,HashFunc> _ht;
};
#include "HashTable.h"
#include "common.h"
template>
class UnorderedSet
{
struct SetKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename HashTable::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair Insert(const K& key)
{
return _ht.Insert(key);
}
private:
HashTable _ht;
};
那么说到这里,这个字符串哈希算法应该怎么实现呢?
由此可见上述两种情景都不是最好的解决办法,因此参考字符串哈希函数 字符串哈希函数
我们可以通过下面的代码解决string的哈希映射问题
template<>
struct HashFunc
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (size_t i = 0; i < s.size(); ++i)
{
hash = hash * 131 + s[i];
}
return hash;
}
};
看到这里可能有人会疑惑,既然有了HashFunc这个模板参数了,那我们平时使用unordered_map/set的时候为什么没有传这个参数就可以直接使用了呢?这里就体现了缺省参数和模板特化的强大了
#pragma once
//开散列解决哈希冲突,哈希桶、拉链法
#include
#include
using namespace std;
template
struct HashNode
{
//由于之后要用哈希桶封装unordered_map/set,因此这里可能存pair也可能存value
V _valuefiled;
HashNode* _next;//哈希桶结构是一个vector数组下挂着链表,定义一个指针指向下一个节点的位置
HashNode(const V& v)
:_valuefiled(v)
, _next(nullptr)
{}
};
//前置声明,由于哈希表需要迭代器,迭代器又需要哈希表
template
class HashTable;
//实现哈希表的迭代器
template
struct HTIterator
{
typedef HashNode Node;
typedef HTIterator Self;
Node* _node;//节点指针
HashTable* _ht;//哈希表指针
HTIterator(Node* node, HashTable* ht)
:_node(node)
, _ht(ht)
{}
V& operator*()//返回节点的值
{
return _node->_valuefiled;
}
V* operator->()//返回节点指针
{
return &_node->_valuefiled;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
// 找下一个桶的第一个节点位置
//size_t i = KeyOfValue()(_node->_valuefiled) % _ht->_table.size();
size_t i = _ht->HashIndex(KeyOfValue()(_node->_valuefiled), _ht->_table.size());
++i;
for (; i < _ht->_table.size(); ++i)
{
if (_ht->_table[i] != nullptr)
{
_node = _ht->_table[i];
break;
}
}
if (i == _ht->_table.size())
{
_node = nullptr;
}
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
};
//由于之后要用哈希桶封装unordered_map/set,因此这里可能存pair也可能存value
//由于pair存储比较必须取key才能比较,因此传仿函数取key
//传HashFunc是为了将一个不能直接取模的key类型,eg:string
template
class HashTable
{
typedef HashNode Node;
public:
template
friend struct HTIterator;
typedef HTIterator iterator;
HashTable()
:_size(0)
{}
iterator begin()
{
for (size_t i = 0; i < _table.size(); ++i)
{
if (_table[i] != nullptr)
{
return iterator(_table[i], this);
}
}
return iterator(nullptr, this);
}
iterator end()
{
return iterator(nullptr, this);
}
pair Insert(const V& v)
{
//考虑增容
CheckCapacity();
KeyOfValue kov;
const K& key = kov(v);
//size_t index = key % _table.size();
size_t index = HashIndex(key, _table.size());
Node* cur = _table[index];
while (cur)
{
if (kov(cur->_valuefiled) == key)
return make_pair(iterator(cur, this), false);
cur = cur->_next;
}
//走到这说明没有相同的元素,可以进行插入,
//由于哈希桶不规定产生冲突的序列有序,可以进行头插比较简单
Node* newnode = new Node(v);
newnode->_next = _table[index];
_table[index] = newnode;
++_size;
return make_pair(iterator(newnode,this),true);
}
void CheckCapacity()
{
//当负载因子==1时扩容
if (_table.size() == _size)
{
size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
//这里不像之前开散列一样创建一个新的哈希表
//再调用Insert的原因是旧表的节点可以直接拿到新的vector数组中进行插入
vector newtable;
newtable.resize(newsize);
//遍历旧表,在新的vector数组中找到对应位置,将旧表节点插入
for (size_t i = 0; i < _table.size(); ++i)
{
Node* cur = _table[i];
//将节点从旧表中拆出来,再重新计算节点在新表中的位置进行插入
while (cur)
{
Node* next = cur->_next;
//size_t index = KeyOfValue()(cur->_valuefiled) % newsize;
size_t index = HashIndex(KeyOfValue()(cur->_valuefiled), newsize);
//头插入新表中
cur->_next = newtable[index];
newtable[index] = cur;
cur = next;
}
//将原来的表置空
_table[i] = nullptr;
}
//交换新旧两标的资源,出作用域后新表自动调用析构函数释放旧表资源
_table.swap(newtable);
}
}
Node* Find(const K& key)
{
//size_t index = key % _table.size();
size_t index = HashIndex(key, _table.size());
Node* cur = _table[index];
while (cur)
{
if (KeyOfValue()(cur->_valuefiled) == key)
return cur;
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
//size_t index = key % _table.size();
size_t index = HashIndex(key, _table.size());
Node* cur = _table[index];
Node* prev = nullptr;
while (cur)
{
if (KeyOfValue()(cur->_valuefiled) == key)
{
if (prev == nullptr)//头删
_table[index] = cur->_next;
else
prev->_next = cur->_next;
delete cur;
--_size;
return true;
}
}
return false;
}
size_t HashIndex(const K& key, size_t size)
{
HashFunc hf;
return hf(key) % size;
}
private:
vector _table;//vector中的size为哈希表的大小
size_t _size;//哈希表中存储的有效元素的个数
};
#include "HashTable.h"
#include "common.h"
template>
class UnorderedMap
{
struct MapKeyOfValue
{
const K& operator()(const pair& _kv)
{
return _kv.first;
}
};
public:
typedef typename HashTable, MapKeyOfValue, HashFunc>::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair Insert(const pair& kv)
{
return _ht.Insert(kv);
}
V& operator[](const K& key)
{
std::pair ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
private:
HashTable, MapKeyOfValue,HashFunc> _ht;
};
void TestUnorderedMap()
{
UnorderedMap m;
m.Insert(std::make_pair(15, 15));
m.Insert(std::make_pair(5, 5));
m.Insert(std::make_pair(25, 25));
m[7] = 6;
for (size_t i = 0; i < 11; ++i)
{
m.Insert(make_pair(i, i));
}
UnorderedMap::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
}
void TestUnorderedMap2()
{
UnorderedMap sm;
sm.Insert(make_pair(string("sort"), string("排序")));
sm.Insert(make_pair(string("left"), string("左边")));
sm.Insert(make_pair(string("string"), string("字符串")));
sm.Insert(make_pair(string("insert"), string("插入")));
for (auto& kv : sm)
{
cout << kv.first << ":" << kv.second << endl;
}
}
#include "HashTable.h"
#include "common.h"
template>
class UnorderedSet
{
struct SetKeyOfValue
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename HashTable::iterator iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
pair Insert(const K& key)
{
return _ht.Insert(key);
}
private:
HashTable _ht;
};
void TestUnorderedSet()
{
UnorderedSet s;
s.Insert(1);
s.Insert(5);
s.Insert(3);
s.Insert(10);
s.Insert(5);
s.Insert(19);
s.Insert(3);
s.Insert(2);
s.Insert(8);
for (auto& e : s)
{
cout << e << " ";
}
cout << endl;
}
#pragma once
#include
#include
using namespace std;
template
struct HashFunc
{
const K& operator()(const K& key)
{
return key;
}
};
template<>
struct HashFunc
{
size_t operator()(const string& s)
{
size_t hash = 0;
for (size_t i = 0; i < s.size(); ++i)
{
hash = hash * 131 + s[i];
}
return hash;
}
};