哈希看起来随机,但它是确定性的,也就是说,特定的输入总是产生相同的哈希值。
因此,在想要在哈希表中插入项时,您需要首先为该输入生成哈希。然后,使用该哈希值索引到表中,并在表中的该位置插入您的项。在典型情况下,您有一个被视为键的部分,以及与其关联的一些更多信息(例如,您可以通过名称查找人员,并且每个名称都有关于该人员的信息)。
稍后,当您想要查找(与之关联的)特定键(在本例中为人员)的信息时,输入和哈希键以找到哈希表中正确的位置以查找该信息。
这确实跳过了一些关键细节,例如如何处理两个或更多输入恰好生成相同的散列值(除非您对可允许的输入施加一些限制,否则无法避免)。有各种处理此类情况的方法,例如仅顺序查看表以查找下一个空闲点,重新散列以在表中找到另一个点,或构建类似于散列到相同值的项目的链接列表。
无论如何,应该添加的是,确实存在使用哈希表的用例,使其最终看起来有点像你所推测的那样。例如,当您想要查看哈希表的所有内容(而不仅仅是一次查找一个项目)时,通常确实需要扫描整个表。即使您的哈希表几乎为空,您通常也必须从一端扫描到另一端,查找每个实际使用的条目。当您这样做时,会按照相当随机的顺序获取项目。
这指出了哈希表的另一个缺点-通常需要与单个原始记录进行精确匹配才能正常工作。例如,让我们考虑一些基于我的姓氏进行的查询。假设您对整个姓氏进行了索引,那么找到"Coffin"将很容易-但是至少在大多数普通哈希函数中,搜索以"Cof"开头的内容将明显变慢,就像"查找"Coffin"和"Demming"之间的所有名称"一样。
因此,您有一半的正确性-虽然哈希表通常非常适用于一些特定情况(主要是搜索精确匹配),但您概述的一般思路(浏览整个表以查找数据)几乎是某些其他目的唯一的选择,因此,如果您想支持除精确匹配外的任何内容,则可能更喜欢其他数据结构。
这主要是处理散列表的最常见用法/类型。可以创建哈希函数,至少在不同程度上弯曲(如果不是彻底破坏)这些规则。在大多数情况下,这些涉及一些妥协。例如,给定地理信息作为输入,可以通过简单地截断坐标(或其中一个坐标)来创建哈希(某种程度上),以获得更低精度的相同信息。这至少对信息进行了一定的组织,因此靠近的东西具有类似的哈希值,使查找相邻数据更容易。然而,这通常会导致更多的冲突(例如,您将获得许多项散列到一个值,以便大城市的市中心)。
具体来看通用哈希,它增加了一个额外的元素:不是单个哈希函数,而是一个哈希函数族,从中“随机”选择。当通用哈希用于实现哈希表时(并非总是如此 - 它通常用于诸如消息认证代码之类的东西),通常不会每次插入项目时随机选择哈希函数。相反,通常选择一个哈希,并继续使用它,直到遇到一些固定数量的冲突。然后随机选择另一个哈希函数。
例如,在Cuckoo散列中(可能是最常用的通用哈希),您将哈希键以查找位置。如果已经占用,您就会“清除”那里的现有项,并重新哈希以查找其替代位置。它被插入那里。如果该插槽已经被占用,它将“清除”该插槽中已经存在的项目,然后模式重复。
当您搜索项目时,您会对其进行哈希并查看该位置。如果为空,您立即知道您的项目不存在。如果该插槽已被占用,但不包含您的项目,则重新哈希以查找替代位置。在所使用的所有哈希函数中继续此模式(通常只有两个哈希函数,在Cuckoo哈希的情况下,但您可以明显地使用更多函数进行类似的算法)。
这可能会失败 - 进入无限循环或(几乎等效地)构建超过预设长度的链。在这种情况下,您重新开始,使用不同的哈希函数对表进行重建。
当使用开放式哈希(其中通用哈希是一种形式)时,删除往往是非常棘手的。特别是,我们必须确保在一个位置删除一个项目时,它不是在该位置发生碰撞的一系列项目的开头。在许多情况下,最有效的方法是为插槽添加第三个状态:如果从未被使用,则为空。如果当前已占用,则正在使用中。如果该处已删除物品,则已删除。这样当您搜索项目时,如果遇到“已删除”插槽,则继续搜索您的项目(而如果您到达从未使用过的插槽,则可以立即停止搜索-您的项目显然从未插入)。