1. unordered系列关联式容器
下面来看哈希,首先看关联式容器unorder_map和unorder_set,它们底层是哈希表,用法和map set一样。下面浅浅过一下,它是单向迭代器,因为没有rbegin和rend。也就是红黑树和哈希表实现的map和set用法几乎相同,区别是:1.unorder系列是单向迭代器。2.unorder系列遍历出来不是有序的。下面演示一下:

它只能去重,不能排序,它也是有multi版本的。再演示一下unorder_map:

2.哈希
下面正式看哈希,什么是哈希呢?我们以前遇到的搜索有这样几类:首先是暴力查找,在一个数组里都查,这样非常慢。于是有人衍生出了有序数组的二分查找,但它的前提是排序,而且增删查改不方便,过程中为了保证有序会涉及大量的数据挪动。因此衍生出了平衡搜索树,此时基础上又出现了新的搜索,这种搜索叫哈希(散列)。它的本质是存储的值跟存储位置建立出一个映射关系,什么意思呢,先来看一个计数排序的样例:

有上面这样的一组值,最小的值是15,最大的值是30,总共开了16个空间。然后存映射关系(次数),15映射第一个位置,16映射第二个位置......以后值是多少去对应的位置找,这就是一种哈希(再如图书馆按照字母号找书等)。但上述如果数据太分散空间消耗量会很大,之前的方式在哈希表中叫直接定址法(用于值的分布范围集中),它是根据值相对直接找一个位置,数据太分散不适合。此时若数据比较分散:

还是开固定空间,比如开20个。来个值key,这用除留系数法,通过除留的系数确定位置,满了再考虑扩容。用i=key%20(空间个数),这样算出来第i个位置,然后把值放进去。它的问题是会引发哈希冲突或碰撞,也就是不同的值,可能会映射到同一个位置,值和位置是多对一的关系,比如下图:

27和47模20都是7,在同一个位置,那怎么办?有一种方法是再按照一种规则去找下一个位置存,比如有种方式叫闭散列------开放定址法:1.线性探测 2.二次探测。也就是找的这个位置被占了,再去找其它位置占。还有一种方式叫拉链法,也叫哈希桶。这个位置存指针,把桶用指针链接起来,一个位置有多个值来个多对一的关系,这种方式不影响别人:

3.闭散列------开放定址法
下面来看看闭散列------开放地址法中的线性探测,如图:

空间大小是10,来了一堆值映射到这些位置。在看下图:

现在来了111和1冲突了,放到下一个位置。44和4冲突了,然后不断向后探测都有值,找到8位置有空位就把值放进去了。如果走完了没找到空位置,要立刻扩容吗?比如下图场景:

只有19个,现在来了个29,往后走越界了,不需要立刻扩容,绕到开头找空位放。再看一个问题,下图中想查找44怎么查找:

44%10算出在4的位置,但44不在这个位置,不一定停,可能在下一个位置,就不断往后找,找到8位置就找到了。如果找的是54,模出来在4位置,不是54往后走,找到空位置可以停下来。侧面反映出如果表太满了,此时查找一些不存在的值效率必然下降,接近暴力查。还有个问题是比如要删6,是找到6在的位置然后删6吗:

这样会有些问题:1.把6删了该位置的值抹成0还是随机值?2.此时查44会发生什么?找到空就结束了,这样删除影响了查找。怎么解决?这里要想办法用一个标记,用状态标记,这严格说有三种状态:1.EXIST存在 2.EMPTY空 3.DELETE删除。此时想删6,不需要真干了6,把这个状态设置为DELETE,然后查44时遇到DELETE不停止,遇到EMPTY才停止。下面来写一下:还是定义为KV结构,如果自己写就需要写个数组并定义数组大小及容量,现在用vector就行。这里不能直接定义出是一个pair的结构,因为除了KV还有状态,所以枚举状态。再定义一个哈希的数据,给个pair和STATE数据。哈希表中定义一个vector,里面放HashDate,再定义一个n,那vector已有size了为啥还要定义n呢?因为它和顺序表的区别是它的数据要分散存储,它的物理结构是数组,逻辑上是个哈希表,这的n表示存储的有效数据的个数:

vector可以不初始化,因为它有默认构造,n可以给个值。下一步来insert一个值,insert一个pair(先不考虑扩容,把算位置的问题解决了):先算一个起始位置,可以用first模上表的大小。这的大小是size还是capacity呢?假设模的是_table.capacity,模完后刚好那个位置没有被占,就把它的值正常放进去:

假设vector是这样的:

现在比如模出的位置在18,此时18这个位置不能访问,方括号中会断言下标在0~size-1。所以这里不能模capacity,要模size,其次最好让size和capacity相等,这样不存在空间浪费的问题(后面扩容想办法):

下面继续完善,如果这个位置有值要继续探测,探测到空说明没值可以放;遇到这个位置是删除也是可以填值的:

还有个问题是这个表不敢太满,比如只剩下1或2个位置随便来个值都可能冲突,若满了来值还会造成死循环,因此哈希表中引入了一个概念叫负载或载荷因子:

它用来标识存的值占空间的比列。负载因在越大,冲突概率越大,空间利用率越高;负载因为越小,冲突概率越小,空间利用率越低。通过上述介绍可以看到,哈希表不能满了再扩容,控制负载因子到一定值就扩容。下面写写扩容逻辑:如果table大小为0或者有效数据个数除大小大于等于0.7就扩容,为了有小数记得隐式转化为double。但table刚开始为0,可写个构造用resize让size和capacity为10:

现在如果符合条件扩容2倍,可以这样写吗:

用resize开一段大空间,把数据拷贝下来:
现在查找111,111%20==11,会导致去11的位置找。这里扩容不是重点,重点是值的重新映射。因此不能直接用resize下来值,因为所有的值扩容后映射关系变了,需要重新映射。那应该怎么弄?可以去开一个新的vector,把原来的值重新插入到vector里面:

这有两种方法,第一种方法是弄一个newtable,newtable resize一下到当前2倍,下一步依旧遍历旧表,重新映射到新表。但是取出旧表的值映射到信表是很麻烦的,因为要重新算位置,并且可能冲突时可能还要把冲突逻辑走一遍:

那怎么解决呢?我不定义一个vector的newtable,而是定义一个哈希表的newHT,newHT里面有一个vector,把这个vector resize一下:

然后遍历旧表的数据插入到新表即可,如果状态是存在,插入到新表里面,最后旧表不要了,旧表和新表变换:

备注:改个错误,前面没有发现

这里的亮点是再次复用了insert函数,进而复用了线性探测逻辑,空间开好后不走if的逻辑。下面测试一下:

下面写一下查找:来一个key取模算出一个位置,找到空时还没有找到就结束了,否则继续。值为存在状态且如果第一个值等于key就找到了,返回该位置对象,没找到就返回空指针:

再来写一下删除,可以复用find找到要删除的值,如果找到了改为删除即可,--n并返回true;没有找到返回false:

这还是有个小问题,Find这里返回的是HashData类型的指针,万人别人拿上后改状态就比较麻烦了。可以选择Find那里返回一个pair,但是Erase那的ret又不可以了。所以还是这样改:

这里也不可以直接在HashData中把pair的k改成const key,若改了都没有办法赋值。再看下一部分:

还有一个问题是key可能不能取模,整型才能取模,key可能是string或double等。此时就要走两层映射,哈希表里把存的值和位置建立出映射关系。用除留余数法必须是整型,若不是整型想办法让它变为整型,如字符串先和整型建立映射,然后再和存储位置建立映射。这里用仿函数解决问题:

需要写两个,一个是默认的HashFunc,能取模的转化为整型就可以:

哈希中传一个默认的哈希func,里面所有取模的地方多走一层,定义个Hashfunc的对象,调它的():

但是string不能转整型,再写个StringFunc,返回string的第0个字符:

然后HashFunc默认传DefaultHashFunc,若自己传了就用自己的:

再看一个方法:

平时用unorder_map/set也要取模,但用的时候也没有写仿函数,所以这里可用模板特化解决:

这样就可以做到不传仿函数,当K是string时走特化的,其余走正常的。但是string第一个字母ASCII值再这取模也不好,第一个字母相同的都冲突了,可以考虑把所有ASCII值加起来,这样就会好很多:

但可能还会有换顺序的场景:

这样还是会引发很多冲突,所以有人专门对这做了研究叫字符串哈希算法:
各种字符串Hash函数 - clq - 博客园 (cnblogs.com)
https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html我们可以把大佬的方法用一下,每次加之前乘一个131,这样冲突就可以大大减少了:

4.二次探测及拉链法
以上说的方式叫闭散列的开放定址法,特点是从当前空间里去找下一个位置,但它有很强的局限性。以上说的核心方法是线性探测,它的特征是当前位置已有值,就去后面依次找其它位置。缺陷是会导致互相堵,此时有人想能不能冲突时让它们往外分散一些,所以有人提出了二次探测。二次探测不是说探测两次,是按二次方的角度探测。比如线性探测是模一个值算出一个位置,若该位置有值不断向后找;二次探测就是每次走i^2探测:

这样多值冲突时会相对分散开。
下面再来说一个方法,能不能不要说你的空间被占了就去占别人的空间,想个办法别互相影响,于是有人提出了拉链法,也叫哈希桶。它是把数组弄成指针数组,数组中不存值了,位置冲突了内部消化。如下图:

比如来了1,开一个结点把1挂起来;来了111,继续开一个结点挂起来。也就是冲突的值不是用探测的方式解决,而是用一个链表把它挂起来。下面先来搭个框架(为了防止命名冲突,前面的方法用open_address括起来,下面的方法叫hash_backet):现在的重点是,HashTable第一层基础结构是个指针数组,原来按照原生写法要定义二级指针之类的,现在可以用vector,同时也要给n记录存储了多少个有效数据。顺便写一下构造:

下面实现一下insert,先算一下位置,然后头插:

这里让newnode的next指向第一个结点,第一个结点的地址在hashi的位置,newnode此时要成为第一个,把hashi位置的地址改为newnode的就完成头插了:

完成后++n,返回true,最基本的插入逻辑就完成了:

思考一下这里需要扩容吗?需要扩容,假如不扩容有大量数据时单个桶的拉链会特别长,这样查找数据时效率大大下降,和链表没什么差异了。因此还是要控制负载因子,只不过负载因子可控制的大一些,一般负载因子控制在1,意味着平均下来每个桶一个数据(理想状况)。下面实现扩容,如果_n等于_table的size就扩容。还是用原来的思想,newsize是原表的两倍,再弄个新表,把旧表数据变换到新表:

但是这种思路这里不太推荐,因为这里复用insert时会不断的新开结点,最后还要把结点一个个释放了。相当于把这些结点新开了一遍,旧节点又释放了一遍,太浪费了。因此把结点挪下去就可以了:可以定义newTable,resize一下,然后遍历旧链表,把结点迁下来到新表,当前位置迁走后置空一下。都迁走后新表是想要的,变换一下(底层是finish,start,endofstor等的交换):

下面测试一下(顺便写个print函数方面查看):

还有个问题,最后新表结束要去调用析构函数,先调delete[]数组,delete[]中先针对每个数组中的对象调用析构函数,但表上每个对象是内置类型,没有析构函数,因此HashTable是要单独写析构的(后面来说)。
下面来写find,find就是链表的查找,查找到位置以后再遍历链表就行:

实现完find后再补充一下Insert,如果值已经有了就别插入了,返回false(同时补充之前的insert):

下面来实现删除,这里删除不好复用find,因为要找前一个和后一个,所以还是找到要删除的位置,再找到前一个:

但是有个坑:

当cur删除1位置时,此时prev是空,表示头删,所以分两种情况写:

再看下一个问题:

之前写的开放定址法的哈希不用写析构,vector调用自己的析构时全都干了。哈希桶这里要写析构,因为这只会释放vector的空间,不会把桶释放了,桶是内置类型,所以自己实现:

还有像string这样的类型也要解决,可以把前面写的DefaultHashFunc写到全局,这样大家都可以用,然后再改一下(所有取模的地方用仿函数转化一下):

5.完整代码
//HashTable.h
#pragma once
#include <vector>
#include <string>
#include <stdio.h>
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
//struct StringHashFunc
//{
// size_t operator()(const string& str)
// {
// return str[0];
// }
//};
template<>
struct DefaultHashFunc<string>
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;
hash += ch;
}
return hash;
}
};
namespace open_address
{
enum STATE
{
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
STATE _state = EMPTY;
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
public:
HashTable()
{
_table.resize(10);
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
//扩容
//if((double)_n / (double)_table.size() >= 0.7)
if (_n * 10 / _table.size() >= 7)
{
size_t newSize = _table.size() * 2;
//遍历旧表,重新映射到新表
HashTable<K, V, HashFunc> newHT;
newHT._table.resize(newSize);
//遍历旧表的数据插入到新表即可
for (size_t i = 0; i < _table.size(); i++)
{
if (_table[i]._state == EXIST)
{
newHT.Insert(_table[i]._kv);
}
}
_table.swap(newHT._table);
}
////扩容
////if((double)_n / (double)_table.size() >= 0.7)
//if (_n*10 / _table.size() >= 7)
//{
// size_t newSize = _table.size() * 2;
// vector<HashData> newtable;
// newtable.resize(newSize);
// //遍历链表,重新映射到新表
//}
//线性探测
HashFunc hf;
size_t hashi = hf(kv.first) % _table.size();
while (_table[hashi]._state == EXIST)
{
++hashi;
hashi %= _table.size();
}
_table[hashi]._kv = kv;
_table[hashi]._state = EXIST;
++_n;
return true;
}
HashData<const K, V>* Find(const K& key)
{
//线性探测
HashFunc hf;
size_t hashi = hf(key) % _table.size();
while (_table[hashi]._state != EMPTY)
{
if (_table[hashi]._state == EXIST
&& _table[hashi]._kv.first == key)
{
return (HashData<const K, V>*) & _table[hashi];
}
++hashi;
hashi %= _table.size();
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<const K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
return false;
}
private:
vector<HashData<K, V>> _table;
size_t _n = 0; //存储有效数据的个数
};
}
namespace hash_backet
{
template<class K, class V>
struct HashNode
{
pair<K, V> _kv;
HashNode<K, V>* _next;
HashNode(const pair<K, V>& kv)
:_kv(kv)
, _next(nullptr)
{}
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
HashTable()
{
_table.resize(10, nullptr);
}
~HashTable()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
HashFunc hf;
//负载因子到1就扩容
if (_n == _table.size())
{
size_t newSize = _table.size() * 2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
//遍历旧表,顺手牵羊,把结点迁下来移到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
//头插到新表
size_t hashi = hf(cur->_kv.first) % newSize;
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kv.first) % _table.size();
//头插
Node* newnode = new Node(kv);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return true;
}
Node* Find(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
return nullptr;
}
bool Erase(const K& key)
{
HashFunc hf;
size_t hashi = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
if (prev == nullptr)
{
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
void Print()
{
for (size_t i = 0; i < _table.size(); i++)
{
printf("[%d]->", i);
Node* cur = _table[i];
while (cur)
{
cout << cur->_kv.first << "->";
cur = cur->_next;
}
printf("NULL\n");
}
}
private:
vector<Node*> _table; //指针数组
size_t _n; //存储了多少个有效数据
};
}
6.封装
下面开始进行封装,先来搭一下set架子,它里面是个哈希表,用桶的方法。先改一下:

因为现在不确定是K还是KV的,是要用第二个模板参数来确定是K还是KV的。对于set而言是K,对于map而言是个pair:

再回顾一下为啥需要第一个模板参数呢:

因为若没有K find和erase都不好写,假如写成T:

对set可以,对于map不可以,难道要传个pair?显然不合适。然后都跟着改一下,插入的是一个data:

find这里还要补充keyofT:

补完后都跟着改了:

改正个错误:

下面实现set和map的插入:

下面来实现迭代器,对哈希表而言要把迭代器封装一下:

begin后找到第一个桶的位置,++找node的next就可往下走。那当前桶走完了怎么走到下一个桶?需要用表帮我们找下一个桶。先实现起来走着看,里面需要一个结点指针,迭代器需要用结点指针来构造:

下面看operator++,简单实现是如果_node的next不为空,就继续往下走;若为空,就相当于一个桶走完了,那就要找后一个桶。找后一个桶时先算一下自己当前在几号桶,可提前存一个下标以便知道在几号桶:

除了下标在哪知道外,还需要哈希表对象,因为光有下标也没法往后走,因此最好都传过来,定义一个HashTable的指针:

有了上述后来看下图:

这样可算出当桶的位置,算完后++hashi,从下一个位置查找下一个不为空的桶。如果桶不为空就break找到了,为空就++:

循环出来要么找到了,要么找到了结尾也没有找到,所以这样写:

这样不用外面再判断了,循环出来说明所有桶都走完了。若走完了找不到让空充当结束位置:

下面进一步完善一下:

现在有了++和构造,再来补充一下!=和==:

下面实现begin,找第一个桶就行,遍历找,找到第一个桶后就构造一个迭代器返回,第一个是个结点指针,第二个可传this,this就是哈希表指针,没找到就返回空:

再来实现end,构造一个空的返回去:

下面先从set中套一层,再从map中套一层:

但这里还是有相互依赖的问题:

HashTable里用迭代器没事,因为迭代器的实现在前面,HTIterator里面会用HashTable。这样谁在前面都会有问题,因此可以做一个前置声明,就是告诉你HashTable是定义了的:

最后再实现一下operator*和operator->:

测试时会遇到这样的问题:

说是类外面不能访问私有,可以弄一个友元,类模板这友元带模板参数:

可以测试看一下(erase最后补一下--_n):

下面来解决const迭代器的问题:

可以被修改是一个问题,我们的目标是key不能被修改。还是老方法,弄个const迭代器,控制返回值T就可以了,所以多加参数改一下:

现在把const迭代器外层壳套上了,开始套里面的:

现在先完成set,set这K不允许修改,可以这样:

这样普通和const对象都可以调用。下面编译一下:

报错说构造有问题,const修饰*this,因为const修饰后传的时候是这样:

权限放大传不过去。可以考虑重载一下,把成员遍量也改成const的:

下面来看map的,还是老样子,pair的位置改为const K:

再改一下返回值问题:

再完善insert这里,插入失败返回false;若是新插入的返回新结点和true:

改完insert后其余位置就要变了:

编译后set出现了老问题:

set调用的是哈希表中的insert:

set这里看起来没有问题,我调用你的,你返回pair类型,类型看起来是完全匹配的。实际上上框的代码是unordered_set里面的,下框的代码是hashtable里面的。hashtable里面的就是个普通迭代器,set里看起来是普通,实际上是const迭代器。也就是这两个pair是不一样的pair类型,所以它报错了。这里再把map中的const迭代器加一下:

map不报错是因为map这的iterator就是iterator。那怎么让一个类型转化为另一个类型呢?可以模仿之前的思路,我们需要pair中的普通迭代器可转化为const迭代器,因此就支持一个相关的构造函数:

当你是const迭代器时这是一个构造,当你是普通迭代器时是拷贝构造。改一下错误:

再看个问题,这的写法和上次的不一样:

上次的写是先接收pair,再用pair参数构造,相当于把两个参数提取出来,单独调用pair构造。这里相当于编译器不严格的检查,因为毕竟是不同类型的iterator,显然中间支持了转化,保险起见这样写肯定没有问题:

为什么要大费力气的把insert返回一个pair呢?因为要实现operator[]:

7.完整代码
//unordered_map.h
#pragma once
#include "HashTable.h"
namespace yxx
{
template<class K, class V>
class unordered_map
{
struct MapKeyOfT
{
const K& operator()(const pair<const K, V>& kv)
{
return kv.first;
}
};
public:
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator const_iterator;
iterator begin()
{
return _ht.begin();
}
iterator end()
{
return _ht.end();
}
const_iterator begin() const
{
return _ht.begin();
}
const_iterator end() const
{
return _ht.end();
}
pair<iterator, bool> insert(const pair<K, V>& kv)
{
return _ht.Insert(kv);
}
V& operator[](const K& key)
{
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
return ret.first->second;
}
private:
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};
}
//unordered_set.h
#pragma once
#include "HashTable.h"
namespace yxx
{
template<class K>
class unordered_set
{
struct SetKeyOfT
{
const K& operator()(const K& key)
{
return key;
}
};
public:
typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename hash_bucket::HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;
iterator begin() const
{
return _ht.begin();
}
iterator end() const
{
return _ht.end();
}
pair<iterator, bool> insert(const K& key)
{
//return _ht.Insert(key);
pair<typename hash_bucket::HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
return pair<const_iterator, bool>(ret.first, ret.second);
}
private:
hash_bucket::HashTable<K, K, SetKeyOfT> _ht;
};
}
//HashTable.h
#pragma once
#include <vector>
#include <string>
#include <stdio.h>
#include <string>
template<class K>
struct DefaultHashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
//struct StringHashFunc
//{
// size_t operator()(const string& str)
// {
// return str[0];
// }
//};
template<>
struct DefaultHashFunc<string>
{
size_t operator()(const string& str)
{
size_t hash = 0;
for (auto ch : str)
{
hash *= 131;
hash += ch;
}
return hash;
}
};
namespace open_address
{
enum STATE
{
EXIST,
EMPTY,
DELETE
};
template<class K, class V>
struct HashData
{
pair<K, V> _kv;
STATE _state = EMPTY;
};
template<class K, class V, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
public:
HashTable()
{
_table.resize(10);
}
bool Insert(const pair<K, V>& kv)
{
if (Find(kv.first))
{
return false;
}
//扩容
//if((double)_n / (double)_table.size() >= 0.7)
if (_n * 10 / _table.size() >= 7)
{
size_t newSize = _table.size() * 2;
//遍历旧表,重新映射到新表
HashTable<K, V, HashFunc> newHT;
newHT._table.resize(newSize);
//遍历旧表的数据插入到新表即可
for (size_t i = 0; i < _table.size(); i++)
{
if (_table[i]._state == EXIST)
{
newHT.Insert(_table[i]._kv);
}
}
_table.swap(newHT._table);
}
////扩容
////if((double)_n / (double)_table.size() >= 0.7)
//if (_n*10 / _table.size() >= 7)
//{
// size_t newSize = _table.size() * 2;
// vector<HashData> newtable;
// newtable.resize(newSize);
// //遍历链表,重新映射到新表
//}
//线性探测
HashFunc hf;
size_t hashi = hf(kv.first) % _table.size();
while (_table[hashi]._state == EXIST)
{
++hashi;
hashi %= _table.size();
}
_table[hashi]._kv = kv;
_table[hashi]._state = EXIST;
++_n;
return true;
}
HashData<const K, V>* Find(const K& key)
{
//线性探测
HashFunc hf;
size_t hashi = hf(key) % _table.size();
while (_table[hashi]._state != EMPTY)
{
if (_table[hashi]._state == EXIST
&& _table[hashi]._kv.first == key)
{
return (HashData<const K, V>*) & _table[hashi];
}
++hashi;
hashi %= _table.size();
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<const K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_n;
return true;
}
return false;
}
private:
vector<HashData<K, V>> _table;
size_t _n = 0; //存储有效数据的个数
};
}
namespace hash_bucket
{
template<class T>
struct HashNode
{
T _data;
HashNode<T>* _next;
HashNode(const T& data)
:_data(data)
, _next(nullptr)
{}
};
//前置声明
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;
template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
struct HTIterator
{
typedef HashNode<T> Node;
typedef HTIterator<K, T, Ptr, Ref, KeyOfT, HashFunc> Self;
typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;
Node* _node;
const HashTable<K, T, KeyOfT, HashFunc>* _pht;
HTIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
:_node(node)
,_pht(pht)
{}
HTIterator(const Iterator& it)
:_node(it._node)
, _pht(it._pht)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
Self& operator++()
{
if (_node->_next)
{
_node = _node->_next;
}
else
{
KeyOfT kot;
HashFunc hf;
size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
//从下一个位置查找查找下一个不为空的桶
++hashi;
while (hashi < _pht->_table.size())
{
if (_pht->_table[hashi])
{
_node = _pht->_table[hashi];
return *this;
}
else
{
++hashi;
}
}
_node = nullptr;
}
return *this;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}
bool operator==(const Self& s)
{
return _node == s._node;
}
};
template<class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
typedef HashNode<T> Node;
//友元声明
template<class K, class T, class Ptr, class Ref, class KeyOfT, class HashFunc>
friend struct HTIterator;
public:
typedef HTIterator<K, T, T*, T&, KeyOfT, HashFunc> iterator;
typedef HTIterator<K, T, const T*, const T&, KeyOfT, HashFunc> const_iterator;
iterator begin()
{
//找第一个桶
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return iterator(cur, this);
}
}
return iterator(nullptr, this);
}
iterator end()
{
return iterator(nullptr, this);
}
const_iterator begin() const
{
//找第一个桶
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
if (cur)
{
return const_iterator(cur, this);
}
}
return const_iterator(nullptr, this);
}
const_iterator end() const
{
return const_iterator(nullptr, this);
}
HashTable()
{
_table.resize(10, nullptr);
}
~HashTable()
{
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
delete cur;
cur = next;
}
_table[i] = nullptr;
}
}
pair<iterator, bool> Insert(const T& data)
{
KeyOfT kot;
iterator it = Find(kot(data));
if (it != end())
{
return make_pair(it, false);
}
HashFunc hf;
//负载因子到1就扩容
if (_n == _table.size())
{
size_t newSize = _table.size() * 2;
vector<Node*> newTable;
newTable.resize(newSize, nullptr);
//遍历旧表,顺手牵羊,把结点迁下来移到新表
for (size_t i = 0; i < _table.size(); i++)
{
Node* cur = _table[i];
while (cur)
{
Node* next = cur->_next;
//头插到新表
size_t hashi = hf(kot(cur->_data)) % newSize;
cur->_next = newTable[hashi];
newTable[hashi] = cur;
cur = next;
}
_table[i] = nullptr;
}
_table.swap(newTable);
}
size_t hashi = hf(kot(data)) % _table.size();
//头插
Node* newnode = new Node(data);
newnode->_next = _table[hashi];
_table[hashi] = newnode;
++_n;
return make_pair(iterator(newnode, this), true);
}
iterator Find(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* cur = _table[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
return iterator(cur, this);
}
cur = cur->_next;
}
return end();
}
bool Erase(const K& key)
{
HashFunc hf;
KeyOfT kot;
size_t hashi = hf(key) % _table.size();
Node* prev = nullptr;
Node* cur = _table[hashi];
while (cur)
{
if (kot(cur->_data) == key)
{
if (prev == nullptr)
{
_table[hashi] = cur->_next;
}
else
{
prev->_next = cur->_next;
}
delete cur;
return true;
}
prev = cur;
cur = cur->_next;
}
--_n;
return false;
}
void Print()
{
for (size_t i = 0; i < _table.size(); i++)
{
printf("[%d]->", i);
Node* cur = _table[i];
while (cur)
{
cout << cur->_kv.first << "->";
cur = cur->_next;
}
printf("NULL\n");
}
}
private:
vector<Node*> _table; //指针数组
size_t _n; //存储了多少个有效数据
};
}