哈希表
有的时候,我们需要使用数组记录下标,但是下标可能会非常大,但是存储的数据却不多,这时我们就需要一个名叫“哈希表”的东西进行使用了。
哈希表的创建
哈希表的原理就是用一个哈希函数,使得一些数的的哈希值都在哈希表的可承受范围之内。哈希表本质上就是一个数组,但是大小并没有这么大,而是通过哈希函数映射成一个较小范围的数。
哈希表的基本结构:
template <typename Ty> // 类型名
class hash_tale {
using value_type = Ty;
using reference = Ty &;
private: // 私有成员
static constexpr auto kMaxN = 114514 + 17;
// 越大越不容易冲突
value_type f[kMaxN] = {}; // 表
value_type hash(value_type x) { // 哈希函数
return x % kMaxN;
}
public:
// 重载下标运算符,注意返回的是引用
reference operator[](int x) {
return f[this->hash(x)];
}
};
这里,我们选择的是一个较大的数 \(114514+17\),这个模数越大哈希表冲突的可能性就越小,但是哈希表的内存占用就会越大。如果你需要开二维的哈希表,那么 kMaxN 估计就只能开到 \(1007\) 了。注意我们重载了 operator[] 下标运算符,但是返回的得是引用变量,不然你无法在外部更改值。
冲突的处理
这个哈希表有着很大的缺陷,因为有可能有相同的两个数有着同样的模数,而它们被映射到了同一个下标位置。为了解决冲突,我们可以使用最简单的“线性探查法”。即记录下当前位置所有键值,这样就可以解决冲突的处理。
template <class Key, class Ty>
struct Node {
Key key;
Ty data;
};
template <class Key, class Ty>
class hash_table {
using key_type = Key; // 键值类型
using value_type = Ty; // 数据类型
using size_type = size_t; // 长度类型
using reference = Ty&; // 引用
using const_reference = const Ty&; // 常引用
private:
static constexpr auto kMaxN = 114514 + 17;
vector<Node<Key, Ty> > a[kMaxN];
size_type hash(key_type x); // 哈希函数
public:
reference operator[](key_type x) {
size_type hash_code = this->hash(x);
for (auto i : a[hash_code]) { // 线性探查
if (i.key == x) { // 如果匹配
return i.data; // 返回引用
}
}
value_type temp; // 初始化一个空值
a[hash_code].push_back({hash_code, temp}); // 推进哈希表
return a[hash_code].back().data; // 返回最后一个值
}
};
字符串哈希
如果键值是一个字符串,那我们该怎么办呢?其实我们可以把它看作是一个 \(128\) 进制的数字,然后使用 unsigned 的自然溢出,最后在摸上模数,这样子字符串哈希就完成了。
private: size_type hash(string &s) {
unsigned x = 0;
for (char c : s) {
x *= 128, x += c;
}
return x % kMaxN;
}

浙公网安备 33010602011771号