文章目录
- 十八、哈希
- 3. 实现哈希表
- 哈希表的存储节点
- 哈希函数
- 哈希表的定义
- 哈希表的插入
- 哈希表的查找
- 哈希表的删除
- 测试函数
- 完整代码+结果
- 未完待续
十八、哈希
3. 实现哈希表
哈希表的实现方法有蛮多种,这里我们选一个比较经典的开散列法来实现哈希表。由于STL库里的哈希表都是将负载因子(插入的数据/哈希表的容量)设置为1,所以我们这里也将负载因子设置为1。
哈希表的存储节点
开散列法实现的哈希表,是通过数组来当哈希表,但是数组中存储的都是指针,像一个链表一样,将同索引的数据在这个指针下串联起来。
// 哈希表数据节点类型
template<class K, class V>
struct HashNode
{
// 指向同一个桶的下一个节点
HashNode<K, V>* _next;
// 当前数据
std::pair<K, V> _kv;
HashNode(const std::pair<K, V>& kv)
:_next(nullptr)
,_kv(kv)
{}
};
哈希函数
哈希表是通过哈希函数来确定存储的位置的,但是对于不同的类型,我们无法统一看待。
// 哈希函数
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
// 哈希函数模板特化
template<>
struct HashFunc<std::string>
{
size_t operator()(const std::string& s)
{
// 哈希值
size_t hash = 0;
for (auto e : s)
{
hash += e;
hash *= 131;
}
return hash;
}
};
哈希表的定义
// 哈希表, Hash是仿函数(哈希函数)
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
// 构造函数
HashTable()
{
_tables.resize(10, nullptr);
_n = 0;
}
// 析构函数
~HashTable()
{
// 遍历每个桶
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;
}
}
private:
// 哈希表每个位置都是一个链表(桶)
std::vector<Node*> _tables;
// 数据个数
size_t _n;
};
哈希表的插入
// 插入数据
bool Insert(const std::pair<K, V>& kv)
{
// 该数据存在就不插入
if (Find(kv.first))
return false;
// 创建哈希函数对象
Hash hs;
// 负载因子到1就扩容(满了才扩容)
if (_n == _tables.size())
{
// 创建临时哈希表对象
std::vector<Node*> newTables(_tables.size() * 2, nullptr);
// 遍历每个桶
for (size_t i = 0; i < _tables.size(); ++i)
{
// 遍历桶中的每个数据节点
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// 通过哈希函数计算索引,将旧节点头插到新表
size_t hashi = hs(cur->_kv.first) % newTables.size();
// 新节点指向原本的首元节点
cur->_next = newTables[hashi];
// 头指针指向新节点
newTables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
// 交换表
_tables.swap(newTables);
}
// 通过哈希函数计算索引
size_t hashi = hs(kv.first) % _tables.size();
Node* newnode = new Node(kv);
// 头插
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
// 数据个数+1
++_n;
return true;
}
哈希表的查找
由于哈希表是通过哈希函数来确定存储位置的,所以我们也可以通过哈希函数来找到数据。
// 查找数据
Node* Find(const K& key)
{
// 哈希函数对象
Hash hs;
// 通过哈希函数查找所在的桶
size_t hashi = hs(key) % _tables.size();
// 遍历
Node* cur = _tables[hashi];
while (cur)
{
// 找到了
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
// 没找到
return nullptr;
}
哈希表的删除
// 删除数据
bool Erase(const K& key)
{
// 哈希函数对象
Hash hs;
// 通过哈希函数查找所在的桶
size_t hashi = hs(key) % _tables.size();
// 遍历
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
// 删除
if (prev)
{
prev->_next = cur->_next;
}
else
{
_tables[hashi] = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
测试函数
void TestHT1()
{
HashTable<int, int> ht;
int a[] = { 1,4,24,34,7,44,17,37 };
for (auto e : a)
{
ht.Insert(std::make_pair(e, e));
}
ht.Insert(std::make_pair(5, 5));
ht.Insert(std::make_pair(15, 15));
ht.Insert(std::make_pair(25, 25));
ht.Erase(5);
ht.Erase(15);
ht.Erase(25);
ht.Erase(35);
HashTable<std::string, std::string> dict;
dict.Insert(std::make_pair("sort", "排序"));
dict.Insert(std::make_pair("string", "字符串"));
}
完整代码+结果
HashTable.h:
#pragma once
#include <iostream>
#include <vector>
#include <string>
// 哈希函数
template<class K>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
// 哈希函数模板特化
template<>
struct HashFunc<std::string>
{
size_t operator()(const std::string& s)
{
size_t hash = 0;
for (auto e : s)
{
hash += e;
hash *= 131;
}
return hash;
}
};
namespace hash_bucket
{
// 哈希表数据节点类型
template<class K, class V>
struct HashNode
{
// 指向同一个桶的下一个节点
HashNode<K, V>* _next;
// 当前数据
std::pair<K, V> _kv;
HashNode(const std::pair<K, V>& kv)
:_next(nullptr)
,_kv(kv)
{}
};
// 哈希表
template<class K, class V, class Hash = HashFunc<K>>
class HashTable
{
typedef HashNode<K, V> Node;
public:
// 构造函数
HashTable()
{
_tables.resize(10, nullptr);
_n = 0;
}
// 插入数据
bool Insert(const std::pair<K, V>& kv)
{
// 该数据存在就不插入
if (Find(kv.first))
return false;
// 创建哈希函数对象
Hash hs;
// 负载因子到1就扩容(满了才扩容)
if (_n == _tables.size())
{
// 创建临时哈希表对象
std::vector<Node*> newTables(_tables.size() * 2, nullptr);
// 遍历每个桶
for (size_t i = 0; i < _tables.size(); ++i)
{
// 遍历桶中的每个数据节点
Node* cur = _tables[i];
while (cur)
{
Node* next = cur->_next;
// 通过哈希函数计算索引,将旧节点头插到新表
size_t hashi = hs(cur->_kv.first) % newTables.size();
// 新节点指向原本的首元节点
cur->_next = newTables[hashi];
// 头指针指向新节点
newTables[hashi] = cur;
cur = next;
}
_tables[i] = nullptr;
}
// 交换表
_tables.swap(newTables);
}
// 通过哈希函数计算索引
size_t hashi = hs(kv.first) % _tables.size();
Node* newnode = new Node(kv);
// 头插
newnode->_next = _tables[hashi];
_tables[hashi] = newnode;
// 数据个数+1
++_n;
return true;
}
// 查找数据
Node* Find(const K& key)
{
// 哈希函数对象
Hash hs;
// 通过哈希函数查找所在的桶
size_t hashi = hs(key) % _tables.size();
// 遍历
Node* cur = _tables[hashi];
while (cur)
{
// 找到了
if (cur->_kv.first == key)
{
return cur;
}
cur = cur->_next;
}
// 没找到
return nullptr;
}
// 删除数据
bool Erase(const K& key)
{
// 哈希函数对象
Hash hs;
// 通过哈希函数查找所在的桶
size_t hashi = hs(key) % _tables.size();
// 遍历
Node* prev = nullptr;
Node* cur = _tables[hashi];
while (cur)
{
if (cur->_kv.first == key)
{
// 删除
if (prev)
{
prev->_next = cur->_next;
}
else
{
_tables[hashi] = cur->_next;
}
delete cur;
--_n;
return true;
}
prev = cur;
cur = cur->_next;
}
return false;
}
// 析构函数
~HashTable()
{
// 遍历每个桶
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;
}
}
private:
// 哈希表每个位置都是一个链表(桶)
std::vector<Node*> _tables;
// 数据个数
size_t _n;
};
void TestHT1()
{
HashTable<int, int> ht;
int a[] = { 1,4,24,34,7,44,17,37 };
for (auto e : a)
{
ht.Insert(std::make_pair(e, e));
}
ht.Insert(std::make_pair(5, 5));
ht.Insert(std::make_pair(15, 15));
ht.Insert(std::make_pair(25, 25));
ht.Erase(5);
ht.Erase(15);
ht.Erase(25);
ht.Erase(35);
HashTable<std::string, std::string> dict;
dict.Insert(std::make_pair("sort", "排序"));
dict.Insert(std::make_pair("string", "字符串"));
}
}
test.cpp:
#include <iostream>
#include "HashTable.h"
using namespace std;
int main()
{
hash_bucket::TestHT1();
return 0;
}
结果: