这篇博客要说的是哈希算法,哈希又称为散列,它是将存储的值和存储的位置建立起关联关系的一种算法,或者说是一种将任意长度的数据映射为固定长度的输出的算法。
什么意思呢?我们来看一个例子:比如说我们要存储1,2,3,4,5,6这几个数据,我们可以开6个空间大小的数组用于存储,正好下标(位置)跟数据(值)之间是有一定的关系的,很容易存储。
但是假如是下面6个数据呢?1,2,3,1000,1001,1002难道我们还要开1000个空间不成?当然不可以,这太浪费了,于是就开始想办法,让它们都对于总个数6取余不就得到范围比较小的值了吗。它们取余后分别得到1,2,3,4,5,0,我们让这几个数据当作它们各自的位置,这样每一个数据都和一个位置相互对应了,这就是一种解决问题的方法。
上面两个例子已经很形象的说明了什么是哈希,并且第一个例子就是我们的直接定址法,第二个例子就是除留余数法。
但是我们的除留余数法还是有问题的,有没有可能两个数取余后得到的数相同?肯定是有可能的,这种情况就叫哈希冲突。我们可以知道,哈希冲突越多,那么效率就越低。所以我们一般当负载因子或者叫载荷因子(就是实际存的数据个数除以表的大小)大于某个数就要扩容,增大表的大小。这样就可以一定程度的保证效率。那么接下来我们就要解决哈希冲突,有两种方法,一种叫闭散列开放定址法,一种叫开散列拉链法,也叫哈希桶
它们分别是什么意思呢?下面我们分别来说,闭散列开放定址法就是在这个固定长度的数组中如果一个值要放的位置已经有其他的值了,那么就从这个位置向后边找,直到找到空的位置放入,如果找到结尾,那么再返回头去找。这个向后边找可以一个一个找,就叫线性探测,也可以1,4,9,16.....这样二次方数这样找,就叫做二次探测。
下面一个是哈希桶也叫开散列拉链法,就是我们在哈希表中存某个数据所在节点的指针,如果下个数据仍然在这个位置,那么就挂在上个数据的下边就可以了,挂上数据之后就像一个桶或者像拉链,于是名字由此得名
那么接下来我们就分别用这两种解决哈希冲突的方法来实现一下哈希表,这里我们的哈希表中的值先按整形看,等后边我们再慢慢加上模板等一系列东西,先看第一种方法
enum state {
EMPTY,
EXIST,
DELETE
};
struct HashNode {
int val=0;
state state=EMPTY;
};
class HashTable {
public:
HashTable(size_t n = 10) {
_hashvec.resize(n);
}
HashNode* find(size_t key) {
int hashi = key % _hashvec.size();
while (_hashvec[hashi].state != EMPTY && _hashvec[hashi].val != key) {
hashi++;
hashi %= _hashvec.size();
}
if (_hashvec[hashi].state == EMPTY)return nullptr;
return &_hashvec[hashi];
}
bool insert(size_t data) {
if (find(data))return false;
if (_n * 10 / _hashvec.size() >= 7) {
//扩容
HashTable newtable;
newtable._hashvec.resize(_hashvec.size()*2);
for (size_t i = 0; i < _hashvec.size(); i++) {
if(_hashvec[i].state==EXIST)
newtable.insert(_hashvec[i].val);
}
_hashvec.swap(newtable._hashvec);
}
size_t hashi = data % _hashvec.size();
while (_hashvec[hashi].state == EXIST) {
hashi++;
hashi %= _hashvec.size();
}
_hashvec[hashi].val = data;
_hashvec[hashi].state = EXIST;
_n++;
return true;
}
bool erase(size_t data) {
HashNode* tmp = find(data);
if (tmp == nullptr)return false;
tmp->state = DELETE;
--_n;
return true;
}
private:
size_t _n=0;
vector<HashNode> _hashvec;
};
再看第二种方法
struct HashNode {
HashNode(size_t n=0)
:val(n)
,_next(nullptr){}
size_t val = 0;
HashNode* _next = nullptr;
};
class HashTable {
public:
HashTable(size_t n=10) {
_hashvec.resize(n, nullptr);
}
HashNode* find(size_t key) {
size_t hashi = key % _hashvec.size();
HashNode* cur = _hashvec[hashi];
while (cur) {
if (cur->val == key)return cur;
cur = cur->_next;
}
return nullptr;
}
bool insert(size_t key) {
if (find(key))return false;
if (_n == _hashvec.size()) {
//扩容
HashTable newtable(_hashvec.size() * 2);
for (size_t i = 0; i < _hashvec.size(); i++) {
HashNode* cur = _hashvec[i];
HashNode* next = nullptr;
while (cur) {
next = cur->_next;
size_t hashi = cur->val % newtable._hashvec.size();
cur->_next = newtable._hashvec[hashi];
newtable._hashvec[hashi] = cur;
/*if (newtable._hashvec[hashi] == nullptr) {
newtable._hashvec[hashi] = cur;
}
else {
HashNode* tmp = newtable._hashvec[hashi];
while (tmp->_next) {
tmp = tmp->_next;
}
tmp->_next = cur;
}
cur->_next = nullptr;*/
cur = next;
}
_hashvec[i] = nullptr;
}
_hashvec.swap(newtable._hashvec);
}
size_t hashi = key % _hashvec.size();
HashNode* newnode = new HashNode(key);
newnode->_next = _hashvec[hashi];
_hashvec[hashi] = newnode;
++_n;
return true;
}
bool erase(size_t key) {
size_t hashi = key % _hashvec.size();
HashNode* prev = nullptr;
HashNode* cur = _hashvec[hashi];
while (cur&&cur->val != key) {
prev = cur;
cur = cur->_next;
}
if (cur == nullptr)return false;
if (prev == nullptr) {
_hashvec[hashi] = cur->_next;
}
else {
prev->_next = cur->_next;
}
delete cur;
return true;
}
private:
size_t _n = 0;
vector<HashNode*> _hashvec;
};
}