📜  散列|第一套(介绍)

📅  最后修改于: 2021-10-27 07:23:49             🧑  作者: Mango

假设我们要设计一个系统来存储使用电话号码键入的员工记录。我们希望有效地执行以下查询:

  1. 插入电话号码和相应信息。
  2. 搜索电话号码并获取信息。
  3. 删除电话号码和相关信息。

我们可以考虑使用以下数据结构来维护不同电话号码的信息。

  1. 电话号码和记录数组。
  2. 电话号码和记录的链接列表。
  3. 以电话号码为关键字的平衡二叉搜索树。
  4. 直接访问表。

对于数组和链表,我们需要以线性方式搜索,这在实践中可能代价高昂。如果我们使用数组并保持数据排序,那么可以使用二进制搜索在 O(Logn) 时间内搜索电话号码,但是插入和删除操作变得昂贵,因为我们必须维护排序顺序。

使用平衡二叉搜索树,我们得到适度的搜索、插入和删除次数。所有这些操作都可以保证在 O(Logn) 时间内。

可以想到的另一种解决方案是使用直接访问表,我们在其中创建一个大数组并使用电话号码作为数组中的索引。如果电话号码不存在,则数组中的条目为 NIL,否则数组条目存储指向与电话号码对应的记录的指针。在时间复杂度方面,这个解决方案是最好的,我们可以在 O(1) 时间内完成所有操作。例如,要插入电话号码,我们创建一个包含给定电话号码详细信息的记录,使用电话号码作为索引并将指向创建记录的指针存储在表中。
该解决方案具有许多实际限制。此解决方案的第一个问题是所需的额外空间很大。例如,如果电话号码是 n 位数字,我们需要 O(m * 10 n ) 表空间,其中 m 是要记录的指针的大小。另一个问题是编程语言中的整数可能无法存储 n 位数字。

由于上述限制,不能总是使用直接访问表。散列是几乎可以在所有此类情况下使用的解决方案,并且在实践中与上述数据结构(如数组、链表、平衡 BST)相比性能非常好。通过散列,我们平均(在合理假设下)获得 O(1) 搜索时间,在最坏情况下获得 O(n)。

散列是对直接访问表的改进。这个想法是使用散列函数将给定的电话号码或任何其他键转换为较小的数字,并使用较小的数字作为称为散列表的表中的索引。

Hash函数给定大的电话号码转换为实用小整型值的函数。映射的整数值用作哈希表中的索引。简单来说,哈希函数将一个大数字或字符串映射到一个小整数,该整数可以用作哈希表中的索引。
一个好的散列函数应该具有以下属性
1) 可有效计算。
2) 应该均匀分配钥匙(每个桌子位置每个钥匙的可能性相同)

例如,对于电话号码,一个不好的哈希函数是取前三位数字。一个更好的函数是考虑最后三位数字。请注意,这可能不是最好的散列函数。可能有更好的方法。

哈希表存储指向与给定电话号码对应的记录的指针的数组。如果没有现有电话号码具有等于条目索引的哈希函数值,则哈希表中的条目为 NIL。

冲突处理:由于散列函数我们提供了一个大键的小数字,因此两个键可能会产生相同的值。新插入的键映射到哈希表中已占用槽的情况称为冲突,必须使用某种冲突处理技术进行处理。以下是处理碰撞的方法:

  • 链接:思想是使哈希表的每个单元格指向具有相同哈希函数值的记录的链表。链接很简单,但需要额外的表外内存。
  • 开放寻址:在开放寻址中,所有元素都存储在哈希表本身中。每个表条目包含一条记录或 NIL。在搜索元素时,我们会一一检查表槽,直到找到所需元素或很明显该元素不在表中。

下一篇:
用于碰撞处理的单独链
冲突处理的开放寻址

参考:
麻省理工学院视频讲座

IITD视频讲座

“算法导论”,Thomas H. Cormen、Charles E. Leiserson、Ronald L. Rivest 和 Clifford Stein 第二版

http://www.cs.princeton.edu/~rs/AlgsDS07/10Hashing.pdf

http://www.martinbroadhurst.com/articles/hash-table.html

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程