哈希表(Hash Table)

目录

1.定义

2.哈希函数

3.冲突

3.1.链地址法


1.定义

哈希表(Hash Table)是一种常见的数据结构,用于实现键值对的存储和快速查找。它通过将键映射到表中的一个位置来实现高效的查找操作。

在哈希表中,每个键值对都会经过哈希函数的处理,得到一个哈希值,然后根据哈希值确定该键值对在表中的存储位置。这样可以实现在接近常数时间复杂度内进行插入、删除和查找操作。

哈希表的主要特点包括:

  1. 快速查找: 哈希表可以在平均情况下实现接近常数时间复杂度的查找操作,因为通过哈希函数可以直接定位到存储位置。

  2. 高效的插入和删除: 插入和删除操作也可以在平均情况下实现接近常数时间复杂度,因为哈希表的设计允许快速定位到要操作的位置。

  3. 碰撞处理: 碰撞指的是不同的键经过哈希函数处理后得到相同的哈希值,这时需要采取一些策略来解决碰撞,常见的方法包括链地址法和开放寻址法。

  4. 哈希函数设计: 好的哈希函数应该能够将键均匀地映射到哈希表的不同位置,以减少碰撞的发生。

  5. 空间利用率: 哈希表的空间利用率通常比较高,但在处理碰撞时可能会导致额外的空间开销。

哈希表在实际应用中被广泛使用,例如在编程语言中的字典(Dictionary)、集合(Set)等数据结构中。它提供了快速的查找、插入和删除操作,适用于需要高效查询的场景。

2.哈希函数

哈希函数(Hash Function)是将任意长度的输入数据映射为固定长度的输出数据的函数。在计算机科学中,哈希函数通常用于将数据快速转换为索引,以便在数据结构(如哈希表)中进行快速查找、插入和删除操作。简单说要让键值对应到内存中的位置,就要为键值计算索引,也就是计算这个数据应该放到哪里,这个根据键值计算索引的函数就叫做哈希函数,也称散列函数。

好的哈希函数应该具备以下特点:

  1. 确定性: 相同的输入始终产生相同的输出。

  2. 高效性: 计算速度应该尽可能快。

  3. 均匀性: 输入空间中的不同值应该能够均匀地映射到输出空间,以减少碰撞的可能性。

  4. 抗碰撞性: 最小化碰撞的概率,即不同的输入值映射到相同的输出值的概率应该尽可能小。

在设计哈希函数时,需要根据具体的应用场景和需求选择合适的哈希函数。对于哈希表等数据结构,选择一个良好的哈希函数尤为重要,可以减少碰撞的发生,提高数据的检索效率。

除了常见的哈希函数外,还有一些特定领域的哈希函数,比如用于密码学的哈希函数,需要满足更高的安全性要求。

3.冲突

如果对于任意的键值,哈希函数计算出来的索引都不相同,那只用根据索引把 (key, value) 放到对应的位置就行了。但实际上,常常会出现两个不同的键值,他们用哈希函数计算出来的索引是相同的。这时候就需要一些方法来处理冲突。

常见的解决冲突的方式包括以下几种:

  1. 链地址法(Chaining): 在链地址法中,哈希表的每个槽(桶)都连接一个链表或其他数据结构,当发生冲突时,新的键值对会被添加到对应槽的链表中。这样,即使发生冲突,仍然可以将多个键值对存储在同一个位置上。

  2. 开放寻址法(Open Addressing): 在开放寻址法中,当发生冲突时,会尝试寻找表中的其他位置来存储冲突的键值对。常见的开放寻址法包括线性探测、二次探测和双重散列等方法。

  3. 再哈希(Rehashing): 当哈希表中的负载因子(存储的元素数量与表大小的比值)超过一定阈值时,可以进行再哈希操作,即创建一个更大的哈希表,并将所有的键值对重新插入到新表中,从而减少冲突的概率。

  4. 建立更好的哈希函数: 通过设计更好的哈希函数,可以减少冲突的发生。一个好的哈希函数应该能够均匀地将键映射到哈希表中的位置,从而减少碰撞的可能性。

  5. 线性探测和二次探测: 在开放寻址法中,线性探测和二次探测是两种常见的冲突解决策略。线性探测是逐个地查看下一个位置,直到找到空槽或者找遍整个表;而二次探测则是通过二次探测函数来计算下一个探测位置。

3.1.链地址法

查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其键值与查询的键值是否一致。如果索引的范围是[1,M],哈希表的大小为N,那么一次插入/查询需要进行期望O(\frac{N}{M})次比较。

const int SIZE = 1000000;
const int M = 999997;

struct HashTable {
  struct Node {
    int next, value, key;
  } data[SIZE];

  int head[M], size;

  int f(int key) { return (key % M + M) % M; }

  int get(int key) {
    for (int p = head[f(key)]; p; p = data[p].next)
      if (data[p].key == key) return data[p].value;
    return -1;
  }

  int modify(int key, int value) {
    for (int p = head[f(key)]; p; p = data[p].next)
      if (data[p].key == key) return data[p].value = value;
  }

  int add(int key, int value) {
    if (get(key) != -1) return -1;
    data[++size] = (Node){head[f(key)], value, key};
    head[f(key)] = size;
    return value;
  }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lin..6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值