目录
operator*() operator->() operator!=()
封装unordered_map和unordered_set
哈希:
通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,我们便可以通过映射关系很快的查找到想要的元素。
哈希表(散列表):
希表(散列表)是一种数据结构,通过哈希(散列)方法,构造出来的结构称为哈希表(Hash Table)(或者称散列表),哈希表查找元素的时间复杂度是O(1)。
哈希函数:
通过哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数。
常见的哈希函数:除留余数法:设哈希表中允许的地址数为m,插入的数据为key,则:
key(关键码对应的哈希地址) = key(关键码) % m
例如:
将要插入的数据先%数组的大小,找到对应的映射位置插入
哈希冲突:
当我们在插入13时,会发现13对应的位置已经有数据了,则就构成哈希冲突
解决哈希冲突两种常见的方法是:闭散列和开散列
闭散列:
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么通过线性探测,或二次探测的方法,找到key存放到冲突位置中的“下一个” 空位置去。
闭散列的结构:
enum State //判断数组位置的状态
{
EMPTY, //空
EXITS, //存在
DELETE, //删除
};
template<class T>
struct HashData
{
T _data;
State _state; //状态
};
template<class K, class T, class KeyOfT> //仿函数,取出数据的关键字
class HashTable
{
typedef HashData<T> HashData;
public:
bool Insert(const T& d); //增
HashTable* Find(const K& key); //查
bool Erase(const K& key); //改
private:
vector<HashData> _tables; //数组
size_t _num = 0; //存了几个有效数据
};
哈希表中存放着一个数组(存放着结点)和有效数据的个数。
哈希表结点中存放的除了数据,还有一个被标记的状态,方便我们实现增删查改的操作。
插入数据
负载因子:负载因=表的数据/表的大小,衡量哈希表满的程度,表越接近满,插入数据越容易冲突,冲突越多,效率越低。
哈希表并不是满了才增容,开放定制法中,一般负载因子到0.7左右就开始增容
负载因子越小,冲突概率越低,整体效率越高,但是负载因子越小,浪费的空间越大
bool Insert(const T& data)
{
KeyOfT koft;
//表为空时增容,或者负载营造到0.7时增容,
if(_tables.size()==0 || _num*10/_tables.size()>=7)
{
//定义一个哈希表的对象,将空间开成旧表的2倍
HashTable<K, T, KeyOfT> newht;
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
newht._tables.resize(newsize);
//遍历旧表的数据,在新表中调用Insert接口,将旧表的元素依次插入的新表
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i]._state == EXITS)
{
newht.Insert(_tables[i]._data);
}
}
//将新表和旧表交换
_tables.swap(newht._tables);
}
//计算出data中的key在表中映射的位置
size_t index = koft(data) % _tables.size();
//找到空的位置
while (_tables[index]._state == EXITS)
{
//如果插入的元素表中已经有了,则返回false
if(kofk(_tables[index]._date)==koft(data))
{
return false;
}
++index;
if(index==_tables.size())
{
index=0;
}
}
//找到插入的位置
_tables[index]._data = data;
_tables[index]._state = EXITS;//将状态标记存在
_num++;
}
查找元素
HashTable* Find(const K& key)
{
KeyOfT koft;
//元素在表中的映射位置
size_t index = key % _tables.size();
//不为空就表示可能还在后面
while (_tables[index]._state != EMPTY)
{
if (koft(_tables[index]._data) == key)
{
if (_tables[index]._state == EXITS)
{
return &_tables[index];
}
//如果状态是删除,就返回nullptr
else if (_tables[index]._state == DELETE)
{
return nullptr;
}
}
++index;
if (index == _tables.size)
{
index = 0;
}
}
}
删除元素
bool Erase(const K& key)
{
//调用Find()查找元素
HashData* ret = Find(key);
//不为空表示元素存在
if (ret)
{
//将删除的元素标记成删除
ret->_state = DELETE;
--_num;
return true;
}
else
{
return false;
}
}
闭散列的开发定制法不是一种好的解决方式,因为他时一种我的位置别站了,我就去抢别人的位置的思路。
开散列
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
哈希表结点
//结点
template <class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
: _data(data)
, _next(nullptr)
{}
};
哈希表的结点除了存放数据外,还存放这下一个结点的指针。
迭代器
迭代器结构:
//两个仿函数,KeyOfT是取到关键子,HashFunc是将除整形外的类型转换成可以取%的整形
template<class K, class T, class KeyOfT, class HashFunc>
class __HTIterator
{
typedef HashNode<T> Node;
typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
typedef HashTable<K, T, KeyOfT, HashFunc> HT;
public:
Node* _node;
HT* _pht;
__HTIterator(Node* node, HT* pht) //构造函数
: _node(node)
, _pht(pht)
{}
//将运算符重载
Self& operator++()
T& operator*()
T* operator->()
bool operator!=(const Self& s) const
};
因为哈希表不是一个连续的结构, 我们没法天然的获取到下一个结点所在的位置,我们需要将哈希表的迭代器封装成一个类,对运算符进行重载。
迭代器中存放着哈希表的结点和哈希表的对象。
operator++()
如何找到迭代器下一个所处的位置呢?
如果当前迭代器指向节点的_next不为空:
如果迭代器所指向的结点的_next不为空,应为我们便可以通过他的_next找到下一个结点。
如果当前迭代器指向节点的_next为空:
如果迭代器所指向的结点的_next为空,我们需要从新计算一下这个桶所在的位置,将桶指向下一个不为Null的桶。
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; 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;
}
operator*() operator->() operator!=()
T& operator*()
{
return _node->_data;
}
T* operator->()
{
return &_node->_data;
}
bool operator!=(const Self& s) const
{
return _node != s._node;
}
开散列的哈希表
哈希表的结构
//两个仿函数,KeyOfT是取到关键子,HashFunc是将除整形外的类型转换成可以取%的整形
template <class K, class T, class KeyOfT, class HashFunc= DefaultHash<K>>
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()
iterator end()
~HashTable() //析构函数
pair<iterator, bool> Insert(const T& data) //增加元素
iterator Find(const K& key) //查找元素
bool Erase(const K& key) //删除元素
private:
vector<Node*> _tables; // 指针数组
size_t _num = 0;
};
仿函数
template <class K>
struct DefaultHash
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
template<>
struct DefaultHash<string>
{
size_t operator()(const string& key)
{
// BKDR
size_t hash = 0;
for (auto ch : key)
{
hash = hash * 131 + ch;
}
return hash;
}
因为我们存的不一定是int 类型,哈希函数采用处理余数法,被%的key必须要为整形才可以处理,此处提供将key转化为整形的方法。
我们将string转换成size_t的类型。
析构函数
将每一个桶中的元素删除,将指针置空
~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;
}
}
begin() end()
iterator end()
{
return iterator(nullptr, this);
}
iterator begin()
{
//找到第一个不为空的桶
for (size_t i = 0; i < _tables.size(); ++i)
{
Node* cur = _tables[i];
if (cur)
{
return iterator(cur, this);
}
}
return end();
}
查找元素( Find() )
iterator Find(const K& key)
{
if (_tables.size() == 0)
{
return iterator(nullptr, this);
}
KeyOfT kot;
HashFunc hf;
//查找Key映射的桶的位置
size_t hashi = hf(key);
hashi %= _tables.size();
Node* cur = _tables[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
//返回cur位置的迭代器
return iterator(cur, this);
}
cur = cur->_next;
}
return iterator(nullptr, this);
}
增加元素( Insert() )
如果插入的元素在表中已经存在,则返回表中的相同的元素的位置。
如果插入的元素在表中不存在,则返回新插入表中的元素的位置。
pair<iterator, bool> Insert(const T& data)
{
HashFunc hf;
KeyOfT kot;
//通过find()来判断表中有没有和data相同的元素
iterator pos = Find(kot(data));
if (pos != nullptr)
{
//如果表中已经有了和data相同的元素,则返回表中和data相同的元素的位置
return make_pair(pos, false);
}
//如果插入有效元素个数和数组的大小相同,就增容
if (_tables.size() == _num)
{
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;
++_num;
return make_pair(iterator(newnode, this), true);
}
删除元素( Erase() )
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;
}
封装unordered_map和unordered_set
unordered_map和unordered_set的底层所使用的都是哈希表
unordered_set
namespace MySet
{
template <class K, class HashFunc = DefaultHash<K>>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename OPEN_HASH::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:
OPEN_HASH::HashTable<K, K, SetKeyOfT, HashFunc> _ht;
};
}
unordered_map
namespace MyMap
{
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 OPEN_HASH::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:
OPEN_HASH::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;
};
}