一.哈希表的介绍
哈希表本质上是一种存储键值对的容器,但是和avl树或者是红黑树比较而言,哈希表的查找要更加直接和快速,我个人感觉有点像打靶一样,直接打入某个位置,如果那个位置已经有元素了,就去附近的位置,直到找到空白的地方,但是这个打靶也是存在着一定的规则,一般情况下都是采用哈希值取模的结果进行打靶。
直接来说它通过哈希函数将键映射到表中的一个位置,从而实现快速的数据插入、查找和删除操作。哈希表的核心思想是利用哈希函数将键的值转换为存储位置的索引,从而实现高效的访问。
在C++中unorder_map的底层就是采用了哈希表
二.哈希表的实现逻辑
首先它是通过管理数组的形式来进行实现,所以肯定要含有vector,不过这个vector里面的类型是什么呢?第一,每一个元素它肯定是存键值对的,所以肯定含有pair<K,V>,然后因为对于每一个元素而言,我们需要知道它的状态,比如EXIST,EMPTY,DELETE,所以说就需要采用结构体和联合的结合来构成一个元素
enum STATE
{
EXIST,
DELETE,
EMPTY
};
template<class K, class V>
class HashData
{
public:
pair<K, V> kv;
STATE state = EMPTY;
};
然后我们管理好vector以后,肯定还要知道这个哈希表中有多少元素,这个时候就需要有一个int n来记录元素的数量了,这就是哈希表的成员变量、
class HashTable
{
private:
vector<HashData<K, V>> data;
int n;
然后就是实现构造函数,我一般实现构造函数就是给data开辟空间和将n进行初始化,将n初始化为0,然后将data开辟10空间
HashTable()
{
data.resize(10);
n = 0;
}
然后写插入函数,对于哈希表的插入而言,第一步肯定是判断哈希表里面是否含有这个元素,如果含有,而插入失败,然后就是将这个kay转化成哈希值了,这里转化成哈希值其实并不是很简单,因为要涉及到很多的数据类型,所以说要让外界传递一个哈希函数,但是对于一些int,或者String的类型来说,可以直接生成对应的哈希指,我们也要构造两个默认的哈希函数,第一个哈希函数就比较简单,直接是将key强转为int类型,然后返回,而对于String类型来说,就是需要采用一定的算法来获得哈希指了,不过这里重要的是要利用将类强制定义为String的形式,就可以使用String了
template<class K>
struct defafunc
{
int operator()(const K&val)
{
return (int)val;
}
};
template<>
struct defafunc< std::string>
{
int operator()( const std::string& str)
{
int hash = 0;
for (auto e : str)
{
hash *= 131;
hash += e;
}
return hash;
}
};
这个比较重要,得到哈希值以后,就将哈希值模上数组长度,得到位置,然后再判断该位置是否还存在其他元素,如果有,就将哈希值加加,但是为了防止越界,每次加完以后进行模上数组长度,直到找到空位,放入pair,然后将位置的状态置为存在
HashFunc func;
int hash = func(val.first);
hash = hash % data.size();
cout << hash << endl;
while (data[hash].state == EXIST)
{
hash++;
hash = hash % data.size();
}
data[hash].kv = val;
data[hash].state = EXIST;
n++;
return true;
当然,实现上面的前提是看数组是否需要扩容,一般情况下当存在的元素处于size大于0.7时就属于过多,此时就需要扩容,扩容的思路就是先拷贝实例化一个当前大小二倍的哈希表,然后将现在存在的元素一个个插入新的表里面,然后将两个哈希表的容器交换,这样也就实现了扩容
if (n * 10 / data.size() > 7)
{
int newsize = data.size() * 2;
HashTable<K, V> copy;
copy.data.resize(newsize);
for (auto e : data)
{
if(e.state == EXIST)
copy.insert(e.kv);
}
copy.data.swap(data);
}
然后是查找,查找一般是给一个key,然后返回这个元素的指针,如果不存在这个元素时,就返回nullptr,查找一般是利用刚刚的规则,先利用哈希函数获得哈希值,然后将哈希值进行判断,得到对应的位置,最后是看看直到为EMPTY时能不能找到对应的key,找不到则返回nullptr,找到则返回对应的元素
HashData<K, V>* find(const K& key)
{
HashFunc func;
int hash = func(key);
if (isEmpty())
{
return nullptr;
}
hash = hash % data.size();
while (data[hash].state != EMPTY)
{
if (data[hash].kv.first == key && data[hash].state == EXIST)
return &data[hash];
hash++;
hash = hash % data.size();
}
return nullptr;
}
删除最简单,先从find里面找到位置,然后将位置状态置为DELETE就行