#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace myhash
{
enum State
{
EMPTY,
EXIST,
DELETE
};
template <class K, class V>
struct HashData
{
pair<K, V> _kv;
State _state;
};
template <class K, class V>
class HashTable
{
public:
HashTable() : _size(0) {}
bool Insert(const pair<K, V>& kv)
{
if (_table.size() == 0 || 10 * _size / _table.size() >= 7)
{
Rehash();
}
size_t index = kv.first % _table.size();
while (_table[index]._state == EXIST)
{
index++;
index %= _table.size();
}
_table[index]._kv = kv;
_table[index]._state = EXIST;
++_size;
return true;
}
HashData<K, V>* Find(const K& key)
{
if (_table.size() == 0)
{
return nullptr;
}
size_t index = key % _table.size();
while (_table[index]._state != EMPTY)
{
if (_table[index]._state == EXIST && _table[index]._kv.first == key)
{
return &_table[index];
}
index++;
index %= _table.size();
}
return nullptr;
}
bool Erase(const K& key)
{
HashData<K, V>* ret = Find(key);
if (ret)
{
ret->_state = DELETE;
--_size;
return true;
}
else
{
return false;
}
}
private:
vector<HashData<K, V>> _table;
size_t _size;
void Rehash()
{
size_t newsize = _table.empty() ? 10 : _table.size() * 2;
HashTable<K, V> newHT;
newHT._table.resize(newsize);
for (auto e : _table)
{
if (e._state == EXIST)
{
newHT.Insert(e._kv);
}
}
_table.swap(newHT._table);
}
};
} // namespace myhash
namespace HashBucket
{
template <class K,class V>
class HashNode
{
pair<K, V> _kv;
HashNode<K,V>* next;
Hashnode()
:_kv(kv)
,next(nullptr)
{}
};
template <class K,class V>
class HashTable
{
typedef HashNode<K, V> node;
public:
//由于编译器会自动生成析构函数对内置类型vector进行析构,但是挂在下面的节点没有释放,因此需要自己手动写一个析构函数
~HashTable()
{
for (int i = 0; i < _table.size(); i++)
{
node* cur = _table[i]
while (cur)
{
node* next = cur->next;
free(cur);
cur = next;
}
_table[i] = nullptr;
}
}
bool Insert(const pair<K, V>& kv)
{
//去重,如果有就不插入
if (find(kv.first))
{
return false;
}
//如果负载因子超过1就进行扩容
if (_size == _table.size())
{
//这里开辟一个新哈希表,然后把原节点重新映射到新表中,最后交换删除
size_t newsize = _size == 0 ? 10 : 2 * _size;
vector<node*> newtable;
newtable.resize(newsize, nullptr);
//重新映射
for (int i = 0; i < _table.size(); i++)
{
node* cur = _table[i];
while (cur)
{
size_t newindex = cur->_kv.first % newsize;
node* next = cur->next;
cur->next = newtable[newindex];
newtable[newindex] = cur;
cur = next;
}
_table[i] = nullptr;
}
//交换删除
_table.swap(newtable);
}
int index = kv.first % _table.size();
//头插
node* newnode = new node(kv);
newnode->next = _table[index];
_table[index] = newnode;
++_size;
return true;
}
node* Find(const K& key)
{
if (_table.size() == 0)
{
return nullptr;
}
int index = key % _table.size();
node* cur = _table[index];
while (cur)
{
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->next;
}
return nullptr;
}
private:
vector<node*> _table;
size_t _size = 0;
};
}
哈希的定义:
顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素
时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即
O(log_2 N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立
一一映射的关系,那么在查找时通过该函数可以很快找到该元素。
当向该结构中:
插入元素
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置
取元素比较,若关键码相等,则搜索成功
该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(Hash Table)(或者称散列表)
当发生哈希冲突时,这里按照最简单的闭散列法中的线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。
线性探测的哈希实现:
当我们使用线性探测法来探测该位置元素是否为空,因此我们就需要额外定义一个表示位来标识该位置是否为空。
这里会存在一个问题,就是有可能这个哈希表已经满了,这个时候while就会死循环,所以我们需要扩容。为了衡量什么时候应该扩容,这里引用了一个参数负载因子:
线性探测优点:实现非常简单,
线性探测缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同
关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降
低。
2. 二次探测
线性探测的缺陷是产生冲突的数据堆积在一块,这与其找下一个空位置有关系,因为找空位
置的方式就是挨着往后逐个去找,因此二次探测为了避免该问题,找下一个空位置的方法
为:$H_i$ = (H_0+ i^2) % m。其中:i = 1,2,3…, H_0是通过散列函数Hash(x)对元素的关键码 key 进行计算得到的位置,m是表 的大小。
但是从本质上来说,这两种方法都是通过寻找下一个空的位置进行占位,没用从根本上解决问题,于是就有了下一种方法:开散列法。
开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地
址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链
接起来,各链表的头结点存储在哈希表中。