哈希表

有的时候,我们需要使用数组记录下标,但是下标可能会非常大,但是存储的数据却不多,这时我们就需要一个名叫“哈希表”的东西进行使用了。

哈希表的创建

哈希表的原理就是用一个哈希函数,使得一些数的的哈希值都在哈希表的可承受范围之内。哈希表本质上就是一个数组,但是大小并没有这么大,而是通过哈希函数映射成一个较小范围的数。

哈希表的基本结构:

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;
}
posted @ 2023-10-15 15:16  haokee  阅读(23)  评论(0)    收藏  举报