5.1 KiB
title | localeTitle |
---|---|
Hash Tables | 哈希表 |
哈希表
散列表(或散列映射)是一种可以将键映射到值的数据结构。散列表使用散列函数来计算索引 进入一个桶阵列,从中可以找到所需的值。定义良好的Hash函数的时间复杂度可以是O(1)。
哈希表(哈希映射)是一种实现关联数组抽象数据类型的数据结构,这种结构可以将键映射到值。哈希表使用哈希函数来计算桶或槽阵列的索引,从中可以找到所需的值。
哈希表的一些重要属性 - 1)值不按排序顺序存储。 2)在哈希表中,还必须处理潜在的冲突。 这通常通过链接来完成,这意味着创建其键映射到特定索引的所有值的链接列表。
哈希表的实现
传统上,哈希表是使用链表列表实现的。 当我们想要插入一个键/值对时,我们使用哈希函数将键映射到数组中的索引。 然后将该值插入该位置的链接列表中。
散列的想法是在一系列桶中分配条目(键/值对)。 给定一个密钥,该算法计算一个索引,该索引建议可以找到条目的位置:
index = f(key, array_size)
通常这分两步完成:
hash = hashfunc(key)
index = hash % array_size
在此方法中,散列与数组大小无关,然后使用模运算符(%)将其缩减为索引(介于0和array_size - 1之间的数字)。
让我们考虑字符串S.您需要计算此字符串中所有字符的频率。
string S = “ababcd”
最简单的方法是迭代所有可能的字符并逐个计算它们的频率。 这种方法的时间复杂度为O(26 * N),其中N是字符串的大小,有26个可能的字符。
void countFre(string S)
{
for(char c = 'a';c <= 'z';++c)
{
int frequency = 0;
for(int i = 0;i < S.length();++i)
if(S[i] == c)
frequency++;
cout << c << ' ' << frequency << endl;
}
}
产量
a 2
b 2
c 1
d 1
e 0
f 0
…
z 0
让我们对这个问题应用哈希。采用大小为26的数组频率,并使用散列函数使用数组的索引散列26个字符。 然后,迭代字符串并增加每个字符的相应索引处的频率值。 这种方法的复杂性是O(N),其中N是字符串的大小。
int Frequency[26];
int hashFunc(char c)
{
return (c - 'a');
}
void countFre(string S)
{
for(int i = 0;i < S.length();++i)
{
int index = hashFunc(S[i]);
Frequency[index]++;
}
for(int i = 0;i < 26;++i)
cout << (char)(i+'a') << ' ' << Frequency[i] << endl;
}
产量
a 2
b 2
c 1
d 1
e 0
f 0
…
z 0
哈希碰撞
当您使用哈希映射时,您必须假设哈希冲突是不可避免的,因为您将使用的哈希映射的大小远小于您拥有的数据量。解决这些冲突的两种主要方法是链接和开放寻址。
链接
解决哈希冲突的一种方法是使用链接。这意味着对于散列表中的每个键值映射,值字段将不仅包含一个数据单元,而是包含数据的链接列表。在下图所示的示例中,您可以看到Sandra Dee在John Smith之后被添加为键152的另一个元素。
关于链接的主要挫折是时间复杂性的增加。这意味着,代替常规哈希表的O(1)属性,每个操作现在将花费更多时间,因为我们需要遍历链表。
打开寻址
解决哈希冲突的另一种方法是使用开放寻址。在此方法中,一旦将值映射到已占用的键,您将以预定的确定方式沿着哈希表的相邻键移动,直到找到具有空值的键。在下图所示的示例中,Sandra Dee被映射到键153,即使她的值应该映射到152。
开放寻址的主要挫折在于,当需要查找值时,它们可能不在您期望的位置(键映射)。因此,您必须遍历散列表的某些部分才能找到您要查找的值,从而导致时间复杂度增加。
时间复杂性
值得注意的是,哈希表具有分摊的常量复杂度,即在平均情况下,复杂度将为O(1)。 在最坏的情况下,如果将太多元素散列到相同的密钥中,则它可以具有O(n)的时间复杂度。
更多信息:
有关哈希表的更多信息 - Wiki 哈希表与STL-map的比较