目录
1.定义
哈希表(Hash Table)是一种常见的数据结构,用于实现键值对的存储和快速查找。它通过将键映射到表中的一个位置来实现高效的查找操作。
在哈希表中,每个键值对都会经过哈希函数的处理,得到一个哈希值,然后根据哈希值确定该键值对在表中的存储位置。这样可以实现在接近常数时间复杂度内进行插入、删除和查找操作。
哈希表的主要特点包括:
-
快速查找: 哈希表可以在平均情况下实现接近常数时间复杂度的查找操作,因为通过哈希函数可以直接定位到存储位置。
-
高效的插入和删除: 插入和删除操作也可以在平均情况下实现接近常数时间复杂度,因为哈希表的设计允许快速定位到要操作的位置。
-
碰撞处理: 碰撞指的是不同的键经过哈希函数处理后得到相同的哈希值,这时需要采取一些策略来解决碰撞,常见的方法包括链地址法和开放寻址法。
-
哈希函数设计: 好的哈希函数应该能够将键均匀地映射到哈希表的不同位置,以减少碰撞的发生。
-
空间利用率: 哈希表的空间利用率通常比较高,但在处理碰撞时可能会导致额外的空间开销。
哈希表在实际应用中被广泛使用,例如在编程语言中的字典(Dictionary)、集合(Set)等数据结构中。它提供了快速的查找、插入和删除操作,适用于需要高效查询的场景。
2.哈希函数
哈希函数(Hash Function)是将任意长度的输入数据映射为固定长度的输出数据的函数。在计算机科学中,哈希函数通常用于将数据快速转换为索引,以便在数据结构(如哈希表)中进行快速查找、插入和删除操作。简单说要让键值对应到内存中的位置,就要为键值计算索引,也就是计算这个数据应该放到哪里,这个根据键值计算索引的函数就叫做哈希函数,也称散列函数。
好的哈希函数应该具备以下特点:
-
确定性: 相同的输入始终产生相同的输出。
-
高效性: 计算速度应该尽可能快。
-
均匀性: 输入空间中的不同值应该能够均匀地映射到输出空间,以减少碰撞的可能性。
-
抗碰撞性: 最小化碰撞的概率,即不同的输入值映射到相同的输出值的概率应该尽可能小。
在设计哈希函数时,需要根据具体的应用场景和需求选择合适的哈希函数。对于哈希表等数据结构,选择一个良好的哈希函数尤为重要,可以减少碰撞的发生,提高数据的检索效率。
除了常见的哈希函数外,还有一些特定领域的哈希函数,比如用于密码学的哈希函数,需要满足更高的安全性要求。
3.冲突
如果对于任意的键值,哈希函数计算出来的索引都不相同,那只用根据索引把 (key, value)
放到对应的位置就行了。但实际上,常常会出现两个不同的键值,他们用哈希函数计算出来的索引是相同的。这时候就需要一些方法来处理冲突。
常见的解决冲突的方式包括以下几种:
-
链地址法(Chaining): 在链地址法中,哈希表的每个槽(桶)都连接一个链表或其他数据结构,当发生冲突时,新的键值对会被添加到对应槽的链表中。这样,即使发生冲突,仍然可以将多个键值对存储在同一个位置上。
-
开放寻址法(Open Addressing): 在开放寻址法中,当发生冲突时,会尝试寻找表中的其他位置来存储冲突的键值对。常见的开放寻址法包括线性探测、二次探测和双重散列等方法。
-
再哈希(Rehashing): 当哈希表中的负载因子(存储的元素数量与表大小的比值)超过一定阈值时,可以进行再哈希操作,即创建一个更大的哈希表,并将所有的键值对重新插入到新表中,从而减少冲突的概率。
-
建立更好的哈希函数: 通过设计更好的哈希函数,可以减少冲突的发生。一个好的哈希函数应该能够均匀地将键映射到哈希表中的位置,从而减少碰撞的可能性。
-
线性探测和二次探测: 在开放寻址法中,线性探测和二次探测是两种常见的冲突解决策略。线性探测是逐个地查看下一个位置,直到找到空槽或者找遍整个表;而二次探测则是通过二次探测函数来计算下一个探测位置。
3.1.链地址法
查询的时候需要把对应位置的链表整个扫一遍,对其中的每个数据比较其键值与查询的键值是否一致。如果索引的范围是[1,M],哈希表的大小为N,那么一次插入/查询需要进行期望次比较。
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;
}
};