一、哈希的基本介绍


性能上

查找数据,哈希时间复杂度为O(1)
大量随机数据插入,map和set性能快
大量随机数据删除,map和set性能快

哈希映射方法

1.直接定址法:每个值都有自己唯一的位置
优点:无哈希冲突,简单且均匀
缺点:如果空间分散会有很大的空间浪费
适应于范围小且连续的情况

2,除留余数法
优点:空间合适且不浪费过多的空间
缺点:可能存在有相同的映射地址(哈希冲突)

哈希大部分都围绕着解决哈希冲突

1.闭散列:冲突后占用下一个空位置
缺点:查找效率变低,互相冲突导致插入逻辑复杂
线性探测:线性找下一个空位置,++
二次探测:平方步调找下一个位置,^2

2.开散列(哈希桶)(拉链法):冲突的位置下加上链表(哈希桶),由此STL中并没有双向迭代器
缺点:链表太长有效率问题
优点:冲突不会相互影响


二、闭散列的哈希表实现


实际中开散列应用情况优于闭散列,点一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include<iostream>
#include<vector>
using namespace std;

template<class K>
struct KeyToCmp //得到可以进行比较的数据
{
size_t operator()(const K& k)
{
return (size_t)k;
}
};

template<>
struct KeyToCmp<string> //特化一个string的仿函数,使得字符也能有其映射的地址
{
size_t operator()(const string& k)
{
size_t hash = 0;
for (auto& e : k)
{
hash += e;
}
return hash;
}
};


enum State
{
EMPTY,
EXIST,
DELETE
};

template<class K,class V>
struct HashData
{
pair<K,V> _kv;
State _state = EMPTY; //通过加入状态成员来方便进行增删查改
};

template<class K,class V,class Hash = KeyToCmp<int>>
class HashTable
{
public:
typedef HashData<K,V> Data;

HashTable() //构造
{
_tables.resize(10);
}


//负载因子 = 表中数据/表的大小 衡量哈希表满的程度
//负载因子越大,插入数据越容易发生哈希冲突,冲突越多,效率越低
//所以哈希表并不是满了才增容,一般负载因子在0.7左右就开始增容
//控制的太小,会使得空间浪费
bool Insert(const pair<K,V>& kv)
{
if (Find(kv.first) != nullptr) //已经有了
return false;

if (_num * 10 / _tables.size() >= 7) //负载因子大了,开始增容
{
HashTable<K, V, Hash> tmp;
tmp._tables.resize(2 * _tables.size()); //新创建一个哈希表,将其数组空间扩大至旧哈希表的两倍

for (auto& e : _tables)
{
if (e._state == EXIST) //如果有数据
tmp.Insert(e._kv); //插入,扩容已经在上面完成了,递归时判断增容条件进不去,不会造成死循环
}
_tables.swap(tmp._tables);
}

Hash get;
size_t index = get(kv.first) % _tables.size();

while (_tables[index]._state == EXIST)
{
index++;
index %= _tables.size();
}

_tables[index]._kv = kv;
_tables[index]._state = EXIST;
_num++;

return true;
}


Data* Find(const K& k)
{

Hash get;
size_t index = get(k) % _tables.size();

while (_tables[index]._state != EMPTY)
{
if (_tables[index]._state == EXIST && _tables[index]._kv.first == k)
return &_tables[index];
index++;
index %= _tables.size();
}
return nullptr; //哈希冲突是连续的,如果第一个后面为空,说明后面也没有冲突的情况,直接返回
}


bool Erase(const pair<K, V>& kv)
{
Data* ret = Find(kv.first);
if (ret)
{
ret->_state = DELETE;
--_num;
return true;
}
else
{
return false;
}
}

private:
vector<Data> _tables;
size_t _num = 0; //存储当前存储有效数据的个数
};


三、开散列模拟实现unordered_xxx


unordered_xxx底层都是由开散列实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#pragma once
#include<iostream>
#include<vector>
using namespace std;

namespace BsyOpen
{
template<class K>
struct GetWay
{
const K& operator()(const K& k)
{
return k;
}
};

template<> //特化处理字符串,使得其返回值能够被模
struct GetWay<string>
{
size_t operator()(const string& k)
{
size_t way = 0;
for (size_t i = 0; i < k.size(); ++i)
{
way *= 131; //BKDR Hash,使得哈希冲突降低
way += k[i];
}
return way;
}
};


template<class T> //一个哈希表节点
struct HashNode
{
T _data;
HashNode<T>* _next;
//我们自己实现的哈希输出,并非输入的顺序,如果要实现按输入顺序输出就要多指针
//HashNode<T>* _linknext; //以按顺序输出
//HashNode<T>* _linkprev; //以删除

HashNode(const T& data)
:_data(data)
,_next(nullptr)
{}
};


// 前置声明一下, 否则在迭代器类中, 无法识别出HashTable是什么
template<class K, class T, class TheKey, class GetWayy> //哈希表
class HashTable;

template<class K,class T,class TheKey,class GetWayy> //哈希表迭代器
struct __HashIterator
{
typedef __HashIterator< K, T, TheKey, GetWayy> Self;
typedef HashTable<K, T, TheKey, GetWayy> HT;
typedef HashNode<T> Node;
Node* _node;
HT* _pht; //以便在后续operator++中起到作用

__HashIterator(Node* node,HT* pht)
:_node(node)
,_pht(pht)
{}

T& operator*()
{
return _node->_data;
}

T* operator->()
{
return &_node->_data;
}

Self operator++()
{
if (_node->_next)
{
_node = _node->_next; //如果桶没走完接着往下走
}
else
{
TheKey thekey;
GetWayy getway;
//计算数值应该位于哪个头节点
size_t i = getway(thekey(_node->_data)) % _pht->_tables.size();
//size_t i = GetWayy()(TheKey()(_node->_data)) % _pht->_tables.size();
i++; //现在i就是下一个需要验证是否为空的数组下标
for (; i < _pht->_tables.size(); ++i)
{
Node* cur = _pht->_tables[i];
if (cur)
{
_node = cur;
return *this;
}
}
//此时说明没有下一个节点了
_node = nullptr;
}
return *this;
}

bool operator!=(const Self& s)
{
return _node != s._node;
}
};


template<class K,class T,class TheKey,class GetWayy> //哈希表
class HashTable
{
public:
friend struct __HashIterator< K, T, TheKey, GetWayy>;
typedef HashNode<T> Node;
typedef __HashIterator< K, T, TheKey,GetWayy> iterator;

iterator begin()
{
for (size_t i = 0; i < _tables.size(); ++i)
{
if (_tables[i])
{
return iterator(_tables[i],this);
}
}
return end();
}

iterator end()
{
return iterator(nullptr,this);
}

~HashTable() //销毁完桶之后,,析构自动销毁this,即vector会随着析构函数销毁
{
Clear();
}

void Clear()
{
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 num) //这是一种算法,使得可以减小哈希冲突
{
const int primecount = 28;
const size_t primelist[primecount] = //二倍关系质数表
{
53,97,193,389,769,
1543,3079,6151,12289,24593,
49157,98317,1996613,393241,786433,
1572869,3145739,6291469,12582917,25165843,
50331653,100663319,201326611,402653189,805306457,
1610612741,3221225473,4294967291
};

for (size_t i = 0; i < primecount; ++i)
{
if (primelist[i] > num)
{
return primelist[i];
}
}
return primelist[primecount - 1]; //如果还大,就一直返回最后一个
}

pair<iterator,bool> Insert(const T& data)
{
TheKey thekey;
GetWayy getway;

if (_num == _tables.size()) //此时负载因子为1,避免以后造成大量冲突,增容
{
vector<Node*> newtable;
size_t newsize = GetNextPrime(_num);
newtable.resize(newsize);
for (size_t i = 0; i < _tables.size(); ++i)
{
Node* cur = _tables[i]; //取每个头
while (cur)
{
//旧表取下重新计算新表中的位置
Node* next = cur->_next;
size_t index = getway(thekey(cur->_data)) % newtable.size(); //新表中的位置
cur->_next = newtable[index]; //头插进去
newtable[index] = cur;
cur = next;
}
_tables[i] = nullptr;
}
_tables.swap(newtable);
}


size_t index = getway(thekey(data)) % _tables.size();

Node* cur = _tables[index];
while (cur)
{
if (thekey(cur->_data) == thekey(data)) //重复了,不插入,结束
return make_pair(iterator(cur,this),false);
else
cur = cur->_next;
}

//检验完后走到这里说明可以插入了
//选择头插,因为有头节点的位置,不用找尾
Node* newnode = new Node(data);
newnode->_next = _tables[index];
_tables[index] = newnode;

++_num;
return make_pair(iterator(newnode, this), true);
}

Node* Find(const K& k)
{
TheKey thekey;
GetWayy getway;
size_t index = getway(k) % _tables.size();
Node* cur = _tables[index];
while (cur)
{
if (thekey(cur->_data) == k)
return cur;
else
cur = cur->_next;
}
return nullptr;
}


bool Erase(const K& k)
{
TheKey thekey;
GetWayy getway;
size_t index = getway(k) % _tables.size();
Node* prev = nullptr;
Node* cur = _tables[index]; //找到需要找到数据的头
while (cur)
{
if (thekey(cur->_data) == k)//找到了
{

if (prev == nullptr) //说明要删除的对象就是第一个节点
_tables[index] = cur->_next;
else
prev->_next = cur->_next;

delete cur;
return true;
}
else
{
prev = cur;
cur = cur->_next;
}
}
return false;
}

private:
vector<Node*> _tables;
size_t _num = 0;
};
}

1.unordered_set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#pragma once
#include"Open_Hash.hpp"

namespace Bsy
{
template<class K, class GetWayy = BsyOpen::GetWay<K>>
class unordered_set
{
struct TheKeyOfSet
{
const K& operator()(const K& k)
{
return k;
}
};


public:
typedef typename BsyOpen::HashTable<K, K, TheKeyOfSet, GetWayy>::iterator iterator;

iterator begin()
{
return _ht.begin();
}

iterator end()
{
return _ht.end();
}

pair<iterator,bool> insert(const K& k)
{
return _ht.Insert(k);
}

private:
BsyOpen::HashTable<K, K, TheKeyOfSet,GetWayy> _ht;
};

void test_unordered_set1()
{
unordered_set<int> s;
s.insert(1);
s.insert(5);
s.insert(6);
s.insert(2);

unordered_set<int>::iterator it = s.begin();
while (it != s.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}

}

2.unordered_map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#pragma once
#include"Open_Hash.hpp"

namespace Bsy
{
template<class K,class V, class GetWayy = BsyOpen::GetWay<K>>
class unordered_map
{
struct TheKeyOfMap
{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;
}
};

public:
typedef typename BsyOpen::HashTable<K, pair<K,V>, TheKeyOfMap, GetWayy>::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);
}

V& operator[](const K& k)
{
//pair<iterator, bool> ret = _ht.Insert(make_pair(k, V()));
//return ret.first->second;
return _ht.Insert(make_pair(k, V())).first->second;
}

private:
BsyOpen::HashTable<K, pair<K, V>, TheKeyOfMap,GetWayy> _ht;
};


void test_unordered_map1()
{
unordered_map<string, string> dict;
dict.insert(make_pair("sort", "paixu"));
dict.insert(make_pair("left", "zuo"));
dict.insert(make_pair("long", "chang"));
dict["left"] = "you";
dict["hash"] = "haxi";

unordered_map<string, string>::iterator it = dict.begin();
while (it != dict.end())
{
cout << it->first <<":"<<it->second<< endl;
++it;
}
cout << endl;
}
}