一、哈希表
1、介绍
哈希表(hash table),又称散列表,它通过建立键 key
与值 value
之间的映射,实现高效的元素查询。具体而言,我们向哈希表中输入一个键 key
,则可以在 O(1) 时间内获取对应的值 value
。
除哈希表外,数组和链表也可以实现查询功能,它们的效率对比如表 6-1 所示。
-
添加元素:仅需将元素添加至数组(链表)的尾部即可,使用 O(1) 时间。
-
查询元素:由于数组(链表)是乱序的,因此需要遍历其中的所有元素,使用 O(n) 时间。
-
删除元素:需要先查询到元素,再从数组(链表)中删除,使用 O(n) 时间。
表 6-1 元素查询效率对比
数组 | 链表 | 哈希表 | |
---|---|---|---|
查找元素 | O(n) | O(n) | O(1) |
添加元素 | O(1) | O(1) | O(1) |
删除元素 | O(n) | O(n) | O(1) |
观察发现,在哈希表中进行增删查改的时间复杂度都是 O(1) ,非常高效。
2、哈希表常用操作
哈希表的常见操作包括:初始化、查询操作、添加键值对和删除键值对等。
/* 使用C++STL 初始化哈希表 */
unordered_map<int, string> map;
/* 添加操作 */
// 在哈希表中添加键值对 (key, value)
map[12836] = "小哈";
map[15937] = "小啰";
map[16750] = "小算";
map[13276] = "小法";
map[10583] = "小鸭";
/* 查询操作 */
// 向哈希表中输入键 key ,得到值 value
string name = map[15937];
/* 删除操作 */
// 在哈希表中删除键值对 (key, value)
map.erase(10583);
哈希表有三种常用的遍历方式:遍历键值对、遍历键和遍历值。
/* 遍历哈希表 */
// 遍历键值对 key->value
for (auto kv: map)
{
cout << kv.first << " -> " << kv.second << endl;
}
// 使用迭代器遍历 key->value
for (auto iter = map.begin(); iter != map.end(); iter++)
{
cout << iter->first << "->" << iter->second << endl;
}
3、哈希表简单实现
我们先考虑最简单的情况,仅用一个数组来实现哈希表。在哈希表中,我们将数组中的每个空位称为桶(bucket),每个桶可存储一个键值对。因此,查询操作就是找到 key
对应的桶,并在桶中获取 value
。
那么,如何基于 key
定位对应的桶呢?这是通过哈希函数(hash function)实现的。哈希函数的作用是将一个较大的输入空间映射到一个较小的输出空间。在哈希表中,输入空间是所有 key
,输出空间是所有桶(数组索引)。换句话说,输入一个 key
,我们可以通过哈希函数得到该 key
对应的键值对在数组中的存储位置。
输入一个 key
,哈希函数的计算过程分为以下两步。
-
通过某种哈希算法
hash()
计算得到哈希值。 -
将哈希值对桶数量(数组长度)
capacity
取模,从而获取该key
对应的数组索引index
。
index = hash(key) % capacity
随后,我们就可以利用 index
在哈希表中访问对应的桶,从而获取 value
。
#include <iostream>
#include <vector>
using namespace std;
/* 键值对 */
struct Pair {
public:
int key;
string val;
Pair(int key, string value):key(key),val(value) {}
};
/* 基于数组实现的哈希表 */
class ArrayHashMap {
private:
vector<Pair*> buckets;
public:
ArrayHashMap();
~ArrayHashMap();
int hashFunc(int key); //哈希函数
string get(int key); //查询操作
void put(int key, string val); //添加操作
void remove(int key); //移除操作
vector<Pair*> pairSet(); //获取所有键值对
vector<int> keySet(); //获取所有键
vector<string> valueSet(); //获取所有值
};
ArrayHashMap::ArrayHashMap()
{
// 初始化数组,包含 100 个桶
buckets.resize(100);
}
ArrayHashMap::~ArrayHashMap()
{
//释放bucket中指针指向的空间
for (auto bucket : buckets)
{
delete bucket;
}
buckets.clear();
}
/* 哈希函数 */
int ArrayHashMap::hashFunc(int key)
{
int index = key % 100;
return index;
}
/* 查询操作 */
string ArrayHashMap::get(int key)
{
int index = hashFunc(key);
Pair* pair = buckets[index];
if (pair == nullptr)
{
return "";
}
return pair->val;
}
/* 添加操作(注意此处没有做哈希冲突处理) */
void ArrayHashMap::put(int key, string val)
{
Pair* pair = new Pair(key, val);
int index = hashFunc(key);
buckets[index] = pair;
}
/* 删除操作 */
void ArrayHashMap::remove(int key)
{
int index = hashFunc(key);
// 释放内存并置为 nullptr
delete buckets[index];
buckets[index] = nullptr;
}
/* 获取所有键值对 */
vector<Pair*> ArrayHashMap::pairSet()
{
vector<Pair*> res