• 检索技术核心学习总结


    一、学习检索技术的必要性分析

    (一)关键原因分析

    学习检索技术(Information Retrieval,IR)具有多种重要的原因,特别是在今天信息爆炸的数字化时代。

    总的来说,学习检索技术有助于提高信息处理和利用的效率,无论是个人生活还是在职业和学术领域中。这些技能可以增强信息搜索、分析和组织的能力,有助于更好地满足各种需求和目标。

    (二)现代业务系统应用举例

    检索技术是许多热门业务系统的底层技术,它们依赖于这些技术来实现高效的信息检索和相关性排序。以下是一些常见的应用领域:

    1. 数据库管理系统:数据库管理系统(DBMS)使用检索技术来处理查询,使用户能够快速检索和检查数据库中的信息。这在企业和组织中用于数据存储和管理非常重要。

    2. 搜索引擎:搜索引擎是信息检索的杰出例子。它们使用检索技术来为用户提供与其搜索查询相关的网页和文档。搜索引擎必须能够快速地索引和检索互联网上的海量信息,并根据相关性对其进行排名。

    3. 广告引擎:在线广告平台使用检索技术来确定广告的展示位置和目标受众。这包括确定广告应该显示在哪里以及向哪些用户展示广告,以提高广告的点击率和转化率。

    4. 推荐引擎:推荐引擎使用检索技术来分析用户的行为和兴趣,然后为他们推荐相关的产品、内容或服务。社交媒体、电子商务网站和流媒体平台都使用这种技术来提高用户参与度和满意度。

    5. 内容管理系统:内容管理系统(CMS)使用检索技术来帮助用户管理和组织其网站或应用程序上的内容。这有助于用户更轻松地创建、编辑和查找信息。

    6. 知识图谱:知识图谱是用于组织和检索知识的底层技术,用于构建智能搜索和问答系统。它们可以帮助机器理解和回答自然语言问题。

    总之,检索技术在许多现代业务系统中发挥着关键作用,帮助这些系统有效地处理和提供信息,从而提高用户体验、增加收入并提供更多价值。这些技术的不断发展也推动了互联网和数字经济的进一步发展。

    (三)简单的知识全景图分析

    我们通过学习极客时间中陈东大佬的《检索技术核心 20 讲》来整体快速了解下检索的学习知识全景图,后续很多学习内容主体也来自于该课程。

    以下是对每个层次的详细分析:

    1. 存储介质层:这是检索技术的基础,因为数据的存储方式直接影响检索效率。了解不同存储介质的特性和优劣势,如磁盘、内存、分布式存储等,对于优化检索性能至关重要。

    2. 数据结构与算法层:数据结构和算法是提高检索效率的关键。对于不同类型的数据和查询,选择合适的数据结构和算法至关重要。这层涉及到对各种数据结构和算法的深入理解和熟练运用。

    3. 检索专业知识层:这一层涵盖了更高级的检索技术,包括工程架构和算法策略。工程架构方面,了解如何构建可扩展性强、高可用性的检索系统至关重要。算法策略方面,需要了解各种检索算法和技术,如倒排索引、文本分析、排序算法等。

    4. 检索技术的应用层:这一层将检索技术应用于实际业务场景,包括搜索引擎、广告引擎和推荐引擎等。不同的应用领域可能有类似的工程架构和算法,但也有各自独特的业务需求和处理流程。学习如何将检索技术应用于这些业务系统是非常实际和有用的。

    总的来说,这种层次结构为学习检索技术提供了清晰的指导,从基础知识到高级应用,帮助人们建立起全面的检索技术知识体系。

    二、基础技术分析

    检索是一种从存储数据的地方高效地获取所需信息的技术检索效率与数据存储方式之间存在紧密联系,而研究不同数据结构的存储特点对检索效率的影响非常重要。

    1. 数据结构选择:不同的数据结构适用于不同的数据存储和检索需求。例如,哈希表适用于快速查找,但不适合范围查询。树结构(如二叉树或B树)适用于范围查询,但可能不如哈希表在单一查找上效率高。因此,了解不同数据结构的特点以及何时使用它们是至关重要的。

    2. 索引结构:在数据库和搜索引擎中,索引结构用于加速数据的检索。不同的索引结构,如倒排索引、B树索引、散列索引等,适用于不同类型的查询和数据。选择正确的索引结构可以显著提高检索效率。

    3. 数据编码和压缩:数据在存储时可以采用不同的编码和压缩技术。这些技术可以减少存储空间,并在一定程度上影响检索速度。了解如何选择和应用数据编码和压缩技术对于优化存储和检索效率至关重要。

    4. 分布式存储:在大规模系统中,数据通常分布在多个节点上。了解分布式存储的原理以及如何有效地检索分布式数据对于构建高性能系统非常重要。

    总之,数据结构和存储特点对检索效率产生重大影响,因此,深入了解这些概念和技术对于设计和优化存储和检索系统至关重要。在不同的应用场景中,选择合适的数据结构和存储方式可以显著提高系统的性能和效率。

    检索的核心思路,其实就是通过合理组织数据,尽可能地快速减少查询范围。也就是说到更多的检索算法和技术,其实它们的本质都是通过灵活应用各种数据结构的特点来组织数据,从而达到快速减少查询范围的目的。

    (一)数组和链表的线性结构检索

    基本分析

    数组和链表是两种不同的线性数据结构,它们的检索效率在某些方面有所不同,取决于具体的操作和使用情境。

    数组的检索效率

    • 随机访问效率高:数组在内存中是连续存储的,这使得随机访问数组中的元素非常高效。只需知道索引即可直接访问该位置的元素,时间复杂度为O(1)。
    • 插入和删除效率低:如果要在数组中插入或删除元素,通常需要将后续元素移动以保持连续性。这样的操作平均时间复杂度为O(n),其中n是数组中的元素数量。

    链表的检索效率

    • 随机访问效率低:链表的元素不是连续存储的,因此要访问链表中的某个元素,必须从头节点或其他已知位置开始遍历链表。因此,随机访问的平均时间复杂度为O(n),其中n是链表的长度。
    • 插入和删除效率高:链表在插入和删除元素时通常非常高效。只需修改节点的指针,而不需要移动元素。这些操作的平均时间复杂度为O(1),前提是可以直接访问要插入或删除的节点。

    综上所述,如果您需要频繁进行随机访问操作,数组通常更有效率。但如果您需要频繁进行插入和删除操作,并且对访问效率要求不那么高,链表可能更合适。在实际应用中,通常会根据具体的操作需求来选择适当的数据结构,或者在需要时考虑使用更高级的数据结构来平衡这些操作的性能。例如,平衡二叉搜索树可以提供较好的插入、删除和查找效率。

    使用二分查找提升数组检索效率

    灵活改造链表提升检索效率

    学习链表是学习如何组织“非连续存储空间”的数据结构。以下是一个简单的改造例子,展示了如何根据实际需求来设计链表的变种,以提升检索效率。

    问题背景:假设您需要设计一个音乐播放列表(或歌曲库),用户可以随机访问歌曲,但您希望尽量减少内存占用。

    传统链表:传统的单向链表需要为每首歌曲分配一个节点,这会浪费大量内存,因为每个节点还需要存储指向下一个节点的指针。

    改进方案:为了减少内存占用并提高检索效率,您可以设计一个变种链表,其中每个节点不仅存储歌曲信息,还存储一定数量的歌曲。这种变种链表可以称为“歌曲块链表”(Song Block Linked List)。

    歌曲块链表的设计

    • 每个节点包含一个小数组(或列表),其中存储一定数量的歌曲。数组的大小可以根据实际需求调整,以权衡内存占用和检索效率。
    • 每个节点还包含指向下一个节点的指针,以便可以遍历整个歌曲块链表。

    检索操作

    • 当用户要随机访问某首歌曲时,首先确定它在哪个节点的小数组中。可以使用二分查找等方法快速定位。
    • 一旦找到所在的节点,就可以在节点内的小数组中进行线性搜索,以查找目标歌曲。

    这种歌曲块链表的设计允许充分利用链表的非连续存储空间特点,减少了内存占用,同时仍然可以实现较快的歌曲检索操作。这个例子展示了如何根据实际需求,结合链表的核心思想,设计出适用的数据结构,以提高检索效率和节省内存。

    (二)树和跳表非线性结构检索

    基本分析

    树和跳表都是非线性数据结构,它们在检索方面具有一定的优势,但在不同情况下可能更适用。以下是对树和跳表在非线性结构检索中的分析:

    树(通常是平衡二叉搜索树)

    优点

    • 高效的检索:平衡二叉搜索树(如AVL树或红黑树)在数据频繁变化时能够保持树的平衡,因此具有高效的检索性能。平均检索时间复杂度为O(log n)。
    • 插入和删除:平衡树对插入和删除操作也有较高的效率。

    适用场景

    • 适用于需要频繁的插入、删除和检索操作的情况,例如数据库索引和有序集合。
    • 对数据的要求较高,需要保持数据的有序性时,平衡树是一个好选择。

    跳表

    优点

    • 高效的检索:跳表是一种数据结构,它通过多层索引实现高效的跳跃式检索。平均检索时间复杂度为O(log n),类似于平衡树。
    • 简单实现:相对于平衡树,跳表的实现相对简单,并且不需要自动平衡。

    适用场景

    • 适用于需要高效检索操作,但对插入和删除操作的性能要求相对较低的场景。
    • 可用于实现有序集合、高性能的跳跃表索引等。

    总结

    • 树和跳表都是非线性数据结构,用于高效检索。它们在平均检索时间复杂度上具有相似的性能。
    • 树适用于需要频繁插入、删除和检索的场景,以及对数据的有序性要求较高的情况。
    • 跳表适用于需要高效检索操作,但对插入和删除操作的性能要求相对较低的情况。跳表的实现较为简单。

    在实际应用中,选择树还是跳表取决于具体的需求和性能要求。如果插入和删除操作频繁,而且需要保持数据有序性,那么平衡树可能更适合。如果主要关注高效的检索操作,并且可以容忍较低的插入和删除性能,那么跳表可能是更好的选择

    树结构如何进行二分查找

    树结构(特别是二叉树)是通过二分查找的方式来进行检索操作的。二分查找是一种高效的查找算法,它适用于有序数据集合,例如有序的树结构。以下是二叉树如何进行二分查找的基本原理:

    二叉树是一种树状数据结构,每个节点最多有两个子节点,通常分为左子树和右子树。

    树中的节点按照某种特定的顺序排列,例如,左子树的节点值小于父节点,右子树的节点值大于父节点(或相反,具体取决于树的性质)。

    二分查找

    • 二分查找是一种分而治之的策略,它从树的根节点开始,逐步地将搜索范围减半,直到找到目标元素或确定它不存在。
    • 从根节点开始,比较目标元素与当前节点的值。
    • 如果目标元素小于当前节点的值,继续在左子树中查找,因为左子树的值都小于当前节点。
    • 如果目标元素大于当前节点的值,继续在右子树中查找,因为右子树的值都大于当前节点。
    • 重复这个过程,直到找到目标元素或达到叶子节点,如果仍然没有找到,就说明目标元素不存在于树中。

    时间复杂度

    • 二分查找在平衡二叉树(例如AVL树)中的时间复杂度为O(log n),其中n是树中节点的数量。这是一种非常高效的检索算法。

    总之,树结构通过利用二分查找的方式,将数据按有序的方式组织,以便高效地进行检索操作。在有序的二叉树中,通过比较目标值和当前节点的值,可以确定搜索方向,并且在每一步中将搜索范围减半,从而实现了快速的查找。这使得二叉树成为一种非常有用的数据结构,用于实现高效的查找和排序操作。

    二叉检索树的检索空间平衡方案

    二叉搜索树(Binary Search Tree,BST)的检索性能在很大程度上取决于树的平衡性。如果树的平衡性较好,检索操作的平均时间复杂度将保持在O(log n)级别。然而,如果BST不平衡,最坏情况下,检索操作可能需要O(n)的时间,这会显著降低其性能。

    为了保持BST的平衡性,可以采用以下平衡方案:

    平衡二叉搜索树(AVL树)

    • AVL树是一种自平衡的BST,它通过在每次插入或删除节点后执行旋转操作来保持平衡。
    • 每个节点都有一个平衡因子,表示其左子树的高度和右子树的高度之差。在插入或删除操作后,平衡因子会被更新,并且根据平衡因子的值,执行单旋转或双旋转来恢复平衡。
    • AVL树的平均检索时间复杂度为O(log n),适用于频繁插入和删除操作的场景。

    红黑树

    • 红黑树是另一种自平衡的BST,它通过着色节点并遵循一组规则来保持平衡。
    • 红黑树的平衡性通过节点的颜色和特定的规则来维护。这些规则包括节点的颜色不能相邻,以及从任何节点到其每个叶子的路径包含相同数量的黑色节点。
    • 红黑树的平均检索时间复杂度为O(log n),并且相对于AVL树,它的插入和删除操作可能稍微高效一些。

    伸展树

    • 伸展树是一种自适应的BST,它在每次检索操作后,通过旋转操作将最近访问的节点移到根节点的位置。这有助于提高最近访问的节点的检索速度。
    • 伸展树的平均检索时间复杂度为O(log n),但它可能在插入和删除操作上具有一些性能开销。

    选择适当的平衡方案取决于您的具体需求和性能要求。AVL树和红黑树通常用于需要强制平衡性的场景,而伸展树则适用于需要优化最近访问的节点的检索操作的场景。不同的平衡方案可能具有不同的权衡点,因此在选择时需要考虑您的应用程序的特定需求。

    跳表如何进行二分查找

    跳表(Skip List)是一种数据结构,它是在有序元素集合上执行高效查找、插入和删除操作的一种方式。跳表的二分查找基于多级索引的思想,以下是跳表如何进行二分查找的基本原理:

    多级索引

    • 跳表包含多个级别(层),每个级别都是一个有序链表,其中包含部分原始数据元素。底层链表包含所有元素,而上层链表则包含底层链表中的一部分元素。
    • 每个级别的链表都是有序的,这意味着在每个级别上都可以执行二分查找。

    查找操作

    • 跳表的查找操作从顶层链表的头部开始,逐级向下移动。在每个级别上,它比较当前节点的值与目标值。
    • 如果当前节点的值小于目标值,它会继续向右移动,直到找到一个大于等于目标值的节点。
    • 如果当前节点的值大于目标值,它会向下移动到下一个级别,然后继续查找。

    优势

    • 跳表中的多级索引允许快速地跳过一些元素,从而将搜索范围缩小到一个较小的区域,类似于二分查找。
    • 跳表的平均检索时间复杂度是O(log n),其中n是元素的数量。这使得它在某些情况下比传统链表更高效。

    总之,跳表通过多级索引的方式,将数据组织成多个有序链表,从而实现了高效的查找操作,类似于二分查找的思想。跳表的平均检索时间复杂度是O(log n),这使得它在某些情况下是一种高效的数据结构,特别是当需要在有序元素集合上执行频繁的查找操作时。跳表还相对容易实现,并且不需要像平衡树那样复杂的平衡算法,因此它在实际应用中具有一定的优势。

    重新回忆下删除和插入操作

    跳表的插入和删除操作相对复杂一些,因为它们不仅需要在底层链表上执行插入和删除,还需要维护多级索引的平衡性。

    插入操作

    1. 首先,要插入一个新元素,需要找到插入位置。从顶层链表的头部开始,逐级向下移动,直到找到需要插入的位置。

    2. 在找到插入位置后,执行插入操作。这涉及到将新元素插入到底层链表中的适当位置。

    3. 接下来,需要考虑维护多级索引的平衡性。为了保持平衡,可以采取以下步骤:

      • 随机决定新元素是否要升级到较高级别的索引。这可以通过投掷硬币或其他随机方法来完成。如果决定升级,将新元素添加到上一级索引中,并重复此步骤,直到不再升级为止。
      • 在每个级别上,确保在插入位置的左边和右边都有足够多的元素,以便索引仍然有效。如果某个级别的链表太短,可以在该级别上进行拆分,将新元素添加到合适的位置,然后重新建立索引。
    4. 完成插入操作后,跳表的结构应该仍然是有序的,并且多级索引应该保持平衡。

    删除操作

    1. 删除操作也需要首先找到要删除的元素所在的位置。从顶层链表的头部开始,逐级向下移动,直到找到要删除的元素。

    2. 在找到要删除的位置后,执行删除操作。这涉及到从底层链表中删除元素,并且可能需要合并或删除相关的索引。

    3. 同样需要维护多级索引的平衡性。为了保持平衡,可以采取以下步骤:

      • 在每个级别上,检查是否需要删除级别上的某些元素,以维持索引的平衡。如果某个级别的链表太短,可以将其删除或与下一个级别合并。
    4. 完成删除操作后,跳表的结构应该仍然是有序的,并且多级索引应该保持平衡。

    需要注意的是,插入和删除操作的实现可能会涉及到一些细节,例如如何处理重复元素或如何处理插入位置和删除位置的边界情况。维护跳表的平衡性也需要仔细考虑,以确保操作的高效性和正确性。不过总体来说,跳表的插入和删除操作可以通过谨慎地执行上述步骤来实现。

    (三)哈希检索

    哈希检索(Hash-Based Retrieval)是一种高效的数据检索方法,它基于哈希函数将关键字映射到特定的数据存储位置。哈希检索通常能够在平均情况下实现O(1)级别的检索效率,使得它成为处理大规模数据集的一种重要技术。

    基本的常识分析

    基本原理
    1. 哈希函数:哈希检索的核心是哈希函数。哈希函数是一个将输入数据(例如关键字)映射到一个固定大小的哈希码或哈希值的函数。这个哈希码通常用作数据的索引。

    2. 哈希表:哈希表是存储数据的数据结构,通常由一个数组构成,每个数组元素称为桶(bucket)。哈希函数将关键字映射到数组的索引位置,从而确定数据应该存储在哪个桶中。

    3. 解决哈希冲突:由于哈希函数的映射可能导致不同的关键字映射到相同的索引位置,这种情况称为哈希冲突。哈希表需要一种方法来解决冲突,通常有两种主要方法:

    • 链地址法:每个桶中存储一个链表或其他数据结构,用于存储具有相同哈希码的关键字的数据。当发生冲突时,新数据将附加到相应的链表上。
    • 开放寻址法:在发生冲突时,尝试在哈希表中的其他位置寻找可用的桶,直到找到一个可用的位置为止。
    特点和优点
    1. 高效的检索:在平均情况下,哈希检索可以实现O(1)级别的检索效率,因为哈希函数直接将关键字映射到数据的存储位置,无需遍历整个数据集。

    2. 适用于大规模数据集:哈希检索适用于大规模数据集,因为它的检索时间不会随着数据规模的增加而线性增长。

    3. 快速插入和删除:哈希表通常支持快速的插入和删除操作,因为它们只需要计算哈希码并将数据存储在特定位置。

    注意事项和限制
    1. 哈希函数设计:设计一个好的哈希函数非常重要,它应该能够将不同的关键字均匀地映射到哈希表的不同位置,以减少冲突。

    2. 哈希冲突处理:需要合适的冲突处理策略,例如链地址法或开放寻址法,以确保数据的正确存储和检索。

    3. 空间要求:哈希表通常需要额外的内存来存储桶和哈希码,因此可能需要更多的内存空间。

    4. 不适用于范围查询:哈希检索通常不适用于范围查询,因为它无法轻松找到一个范围内的数据,而需要遍历整个哈希表。

    总之,哈希检索是一种高效的数据检索方法,特别适用于需要快速查找单个关键字的场景,如查找具有唯一标识符的数据。但需要注意哈希函数的设计和冲突处理以确保正确性和性能。

    扩展知识分析

    Java 8后的HashMap优化:链表转换为红黑树+红黑树退化为链表

    在Java 8之后,Java中的HashMap实现经历了重大改进,包括在链表长度达到一定阈值时将其转换为红黑树,以及在红黑树节点数量降低到一定阈值时将其退化为链表。这个改进主要涉及两个方面:性能和平衡。

    链表转换为红黑树

    当HashMap的链表长度达到一定的阈值(默认为8)时,Java 8引入了树化(Treeify)机制,将链表转换为红黑树。这是为了解决在极端情况下,链表可能变得非常长,导致查找、插入和删除的性能下降。树化的过程包括以下步骤:

    1. 当链表长度达到阈值时,将链表转换为一个红黑树。
    2. 如果哈希表的大小小于64,则会在树化之前扩大哈希表的容量,以确保树化后的负载因子在0.75以下。
    3. 树化后,HashMap会使用红黑树的查找算法,将查找、插入和删除操作的性能从O(n)提高到O(log n)。

    红黑树退化为链表

    为了保持性能和平衡,当红黑树中的节点数量降低到一定阈值(默认为6)时,Java 8引入了退化(Untreeify)机制,将红黑树退化为链表。退化的过程包括以下步骤:

    1. 当红黑树中的节点数量降低到阈值以下时,将红黑树还原为链表。
    2. 如果哈希表的大小小于64,则会在退化之前缩小哈希表的容量,以确保负载因子保持在0.75以下。
    3. 退化后,HashMap会使用链表的查找算法,将查找、插入和删除操作的性能回归到O(n)级别。

    这种树化和退化机制的引入,使得Java中的HashMap在不同负载情况下能够保持较好的性能。在大多数情况下,HashMap能够提供O(1)级别的查找、插入和删除操作,但在极端情况下,它会自动调整数据结构以维持性能。这使得HashMap成为了一个高性能的数据结构,适用于各种应用场景。

    哈希表缺点分析

    哈希表是一种高效的数据结构,但它也有一些缺点,需要根据具体的应用场景来权衡是否使用。以下是哈希表的一些缺点:

    1. 冲突处理:冲突是指两个或多个不同的键被哈希函数映射到了相同的存储位置,这可能会导致性能下降。虽然哈希表有解决冲突的方法,如链地址法或开放寻址法,但在极端情况下,冲突仍然可能发生。

    2. 不支持有序存储:哈希表中的数据是无序的,如果需要对数据进行有序访问或范围查询,哈希表不是最佳选择。有序数组或二叉搜索树等数据结构更适合这些需求。

    3. 不适用于小规模数据集:对于小规模的数据集,哈希表可能因为内存占用较高而不划算。哈希表需要预留一定数量的空间来保持性能,对于小数据集来说,可能会浪费大量的内存。

    4. 哈希函数设计:设计一个好的哈希函数对于哈希表的性能至关重要。一个不好的哈希函数可能导致冲突更频繁,从而降低性能。

    5. 内存占用:为了保持性能,哈希表通常需要具有一定的冗余空间。这意味着在存储相对稀疏的数据时,哈希表可能占用较多的内存。

    6. 不适用于高并发写入场景:在高并发写入场景下,多个线程可能同时尝试修改哈希表,需要采用并发控制机制来保护数据结构的一致性,这会增加复杂性。

    总的来说,哈希表在大多数情况下都是高效的数据结构,但它不是适用于所有场景的通用解决方案。在选择数据结构时,需要综合考虑应用的具体需求和性能要求,有时候有序数组、二叉搜索树或其他数据结构可能更适合特定的应用场景。

    哈希函数的应用举例

    考虑一个具体的业务案例:实现一个简单的电话号码簿(电话簿)。在这个电话簿中,用户可以存储联系人的姓名和电话号码,并且能够通过姓名快速查找相应的电话号码。

    我们将使用Java来实现这个电话号码簿,其中哈希表将用于快速查找联系人的电话号码。

    1. package org.zyf.javabasic.test;
    2. import java.util.HashMap;
    3. /**
    4. * @program: zyfboot-javabasic
    5. * @description: 实现一个简单的电话号码簿(电话簿)。
    6. * 在这个电话簿中,用户可以存储联系人的姓名和电话号码,并且能够通过姓名快速查找相应的电话号码。
    7. * @author: zhangyanfeng
    8. * @create: 2023-09-24 22:50
    9. **/
    10. public class PhoneBook {
    11. private HashMap phoneNumbers;
    12. public PhoneBook() {
    13. phoneNumbers = new HashMap<>();
    14. }
    15. // 添加联系人和电话号码
    16. public void addContact(String name, String phoneNumber) {
    17. phoneNumbers.put(name, phoneNumber);
    18. }
    19. // 根据姓名查找电话号码
    20. public String findPhoneNumber(String name) {
    21. if (phoneNumbers.containsKey(name)) {
    22. return phoneNumbers.get(name);
    23. } else {
    24. return "Contact not found";
    25. }
    26. }
    27. public static void main(String[] args) {
    28. // 创建电话号码簿
    29. PhoneBook phoneBook = new PhoneBook();
    30. // 添加联系人和电话号码
    31. phoneBook.addContact("Alice", "123-456-7890");
    32. phoneBook.addContact("Bob", "987-654-3210");
    33. phoneBook.addContact("Charlie", "555-123-4567");
    34. // 查找联系人的电话号码
    35. System.out.println("Alice's Phone Number: " + phoneBook.findPhoneNumber("Alice"));
    36. System.out.println("Bob's Phone Number: " + phoneBook.findPhoneNumber("Bob"));
    37. System.out.println("Eve's Phone Number: " + phoneBook.findPhoneNumber("Eve"));
    38. }
    39. }

    在这个示例中,我们创建了一个名为PhoneBook的类,它包含一个哈希表phoneNumbers用于存储联系人的姓名和电话号码。addContact方法用于添加联系人和电话号码,findPhoneNumber方法用于根据姓名查找电话号码。

    main方法中,我们创建了一个电话号码簿对象,并添加了几个联系人的信息。然后,我们使用findPhoneNumber方法查找联系人的电话号码,并输出结果。

    这个示例演示了如何使用Java中的HashMap来实现一个简单的电话号码簿,其中哈希表用于快速查找联系人的电话号码。这是一个实际应用中的哈希检索的简单示例。

    (四)状态检索

    基本说明

    状态检索是实际工作中的常见需求,它涉及到判断某个对象或信息是否已存在或已处理。在不同的应用场景中,状态检索可能采用不同的数据结构和算法来实现,具体取决于需求和性能要求。

    以下是状态检索的一些常见应用场景和相应的分析:

    用户注册

    • 场景:在用户注册流程中,需要快速判断一个用户提供的用户名或邮箱是否已经被注册。
    • 实现:通常可以使用哈希表或数据库中的索引来存储已注册的用户名或邮箱。在用户提交注册请求时,可以通过查找哈希表或数据库索引来判断是否已存在。

    网页抓取

    • 场景:在网页抓取系统中,需要判断一个URL是否已经被抓取过,以避免重复抓取。
    • 实现:可以使用布隆过滤器(Bloom Filter)来存储已抓取的URL。布隆过滤器是一种高效的数据结构,用于判断元素是否属于一个集合。在抓取前,可以查询布隆过滤器,如果URL可能已存在,则进行进一步检查;否则,进行抓取操作。

    缓存系统

    • 场景:在缓存系统中,需要检查某个数据是否已经缓存,以避免重新计算或查询数据库。
    • 实现:通常使用缓存数据结构(如哈希表或分布式缓存)来存储已缓存的数据。在需要获取数据时,首先在缓存中查找,如果存在则返回,否则再去源数据源获取并缓存。

    任务队列

    • 场景:在任务队列中,需要判断某个任务是否已经被处理,以避免重复执行。
    • 实现:可以使用消息队列或分布式任务调度系统来管理任务状态。每个任务被处理后,可以标记为已完成或移出队列,以确保不会重复执行。

    文件系统

    • 场景:在文件系统中,需要检查某个文件是否已存在,以避免覆盖或重复创建。
    • 实现:可以使用文件系统的API来查询文件是否存在。一些编程语言和操作系统提供了直接的文件检查方法。

    在实际工作中,状态检索通常需要考虑性能、并发和数据一致性等因素。选择合适的数据结构和算法,以及采用适当的并发控制机制,可以确保状态检索在各种应用场景中高效且可靠地工作。

    位图(Bitmap)和布隆过滤器(Bloom Filter)

    位图(Bitmap)和布隆过滤器(Bloom Filter)是两种高效判断对象是否存在的数据结构,它们在某些状态检索问题中具有独特的优势。

    快速判断一个对象是否存在的问题。相比于有序数组、二叉检索树和哈希表这三种方案,位图和布隆过滤器其实更适合解决这类状态检索的问题。这是因为,在不要求 100% 判断正确的情况下,使用位图和布隆过滤器可以达到 O(1) 时间代价的检索效率,同时空间使用率也非常高效。

    位图(Bitmap)

    原理:位图是一种使用位来表示元素是否存在的数据结构。每个元素都映射到位图中的一个位,如果元素存在,则对应位被设置为1,否则设置为0。

    优点

    • 空间高效:位图非常节省空间,因为每个元素只需要一个二进制位来表示。
    • 快速判断:检查元素是否存在的操作是O(1),只需查询相应位的状态即可。
    • 支持高并发:位图适合在多线程或多进程环境中并发访问,因为位操作通常是原子的。

    局限性

    • 只能用于有限域:位图适用于元素范围有限的情况,比如整数范围。
    布隆过滤器(Bloom Filter)

    原理:布隆过滤器是一种概率型数据结构,它使用多个哈希函数将元素映射到一个位数组上,并将对应位置的位设置为1。当查询某个元素是否存在时,需要通过所有哈希函数来检查位数组中的对应位,只有当所有位都为1时,才认为元素可能存在。

    优点

    • 空间高效:布隆过滤器也非常节省空间,特别适合大规模数据集。
    • 快速判断:查询操作通常是O(1)的,因为只需要检查位数组中的位状态。
    • 可扩展:可以通过调整位数组大小和哈希函数数量来平衡误判率和空间占用。

    局限性

    • 存在误判:由于多个元素可能映射到相同的位,因此布隆过滤器可能会存在一定的误判率。这意味着它在需要百分之百准确性的场景下不适用。
    选择位图或布隆过滤器
    • 如果需要百分之百的准确性,不能容忍误判的情况下,应该选择位图。位图适用于有限域的情况,可以确保元素是否存在的准确性。

    • 如果可以容忍一定的误判率,并且需要处理大规模的数据集,同时对空间占用有限制,布隆过滤器可能是更好的选择。它可以提供O(1)时间复杂度的查询,适用于状态检索等应用。

    总的来说,位图和布隆过滤器都是高效的数据结构,但适用于不同的场景。在选择时,需要根据应用需求、数据规模和空间限制来权衡使用哪种数据结构。

    扩展:布隆过滤器误判率举例分析

    背景

    假设有一个网站,用户可以在该网站上发布文章。为了防止用户发布重复的文章,网站采用了布隆过滤器来记录已经发布过的文章链接。当用户尝试发布一篇新文章时,系统会先使用布隆过滤器来检查这篇文章的链接是否已经存在,以避免重复发布。

    参数

    • 布隆过滤器的位数组大小:1,000,000个位(1百万位)
    • 使用的哈希函数数量:5个
    • 文章链接集合:已发布文章的链接集合

    误判率计算

    误判率通常由布隆过滤器的设计参数和已存储的数据集决定。在这个示例中,误判率可以通过以下方式来估算:

    1. 计算哈希函数的位数组索引位置:当用户尝试发布一篇新文章时,使用5个不同的哈希函数将文章的链接映射到位数组上的5个不同位置。

    2. 检查位数组上的这5个位置是否都为1。如果其中任何一个位置为0,那么系统会判断这篇文章的链接是新的;否则,如果这5个位置都为1,系统可能会误判这篇文章的链接已经存在。

    3. 误判率估算:误判率的估算通常是通过布隆过滤器的公式来计算的,但在这个示例中,我们简单估算一下。假设已发布的文章链接数量是10,000条,那么每个哈希函数的索引位置约为100,000 / 1,000,000 = 0.1,也就是说每个哈希函数在位数组上平均设置了约10%的位。因此,对于一个新链接,如果5个哈希函数都返回0.1,那么它们都会命中已发布的链接,导致误判。

    这个估算是非常简化的,真实的误判率取决于哈希函数的质量、位数组的大小以及已存储的数据集大小等因素。布隆过滤器的误判率通常在可接受范围内,并且可以通过增加位数组大小和哈希函数数量来降低误判率,但这也会增加内存消耗。

    (五)倒排索引

    正排索引理解

    正排索引(Forward Index),与倒排索引相对应,是一种数据结构,用于存储文档集合中的每个文档的详细信息。正排索引是一种文档级别的索引,通常包含了每篇文档的所有内容,或者至少包含了文档的主要信息,例如标题、正文、作者、发布日期等。

    正排索引的主要特点和作用如下:

    1. 文档级别存储:正排索引将每篇文档的信息以文档为单位存储,每个文档在正排索引中有一个唯一的标识符(通常是文档ID)。

    2. 包含详细信息:正排索引通常包含了文档的全部或部分内容,以便可以根据用户的查询需求来检索和展示文档。这使得正排索引非常适合用于展示搜索结果或文档的详细内容。

    3. 不适合词级别检索:与倒排索引不同,正排索引通常不支持词级别的检索。也就是说,正排索引不能像倒排索引那样快速查找包含特定词汇的文档。

    4. 文档关联信息:正排索引中的每个文档可以关联到其它相关信息,例如作者、发布日期、摘要等,这些信息可以用于显示搜索结果的元数据。

    5. 适合文档级别操作:正排索引适合处理文档级别的操作,例如按文档ID查找、按日期排序、展示文档的内容等。

    正排索引和倒排索引通常在搜索引擎和信息检索系统中一起使用。当用户发起查询时,首先使用倒排索引快速找到包含查询词汇的文档ID列表,然后使用正排索引检索这些文档的详细信息,以便展示搜索结果。

    总之,正排索引用于存储和检索文档级别的信息,通常包含了文档的全部或部分内容以及与文档相关的元数据,是搜索引擎和信息检索系统中的重要组成部分。

    倒排索引理解

    倒排索引(Inverted Index)是一种用于快速查找文档、单词或词条出现位置的数据结构,广泛用于搜索引擎和信息检索系统中。它的基本思想是将文档中的内容反向映射到单词或词条上,以便能够快速检索文档包含特定单词或词条的情况。倒排索引包含以下主要组成部分:

    1. 词汇表(Vocabulary):词汇表是所有不重复单词或词条的列表,通常按照字母顺序排序。每个词汇表条目通常会关联到一个或多个文档的位置信息。

    2. 倒排列表(Inverted List):每个词汇表条目都对应一个倒排列表,该列表存储了包含该单词或词条的文档的位置信息。倒排列表通常包括文档ID、位置信息等。

    下面是一个简单的示例来说明倒排索引的工作原理,假设有三篇文档包含以下文本:

    文档1:"This is a sample document."

    文档2:"A sample document is created."

    文档3:"Document creation is done."

    通过构建倒排索引,我们可以得到以下结果:

    • 词汇表:

      • "A"
      • "This"
      • "a"
      • "created"
      • "creation"
      • "document"
      • "done"
      • "is"
      • "sample"
    • 倒排列表:

      • "A": [2]
      • "This": [1]
      • "a": [1, 2]
      • "created": [2]
      • "creation": [3]
      • "document": [1, 2, 3]
      • "done": [3]
      • "is": [1, 2]
      • "sample": [1, 2]

    在上面的示例中,我们可以看到词汇表中的每个词汇对应着包含该词汇的文档的位置信息(文档ID)。通过倒排索引,我们可以轻松地查找包含特定词汇的文档,这大大提高了文本检索的效率。倒排索引在搜索引擎中的应用非常广泛,它允许用户在海量文本数据中快速找到相关信息。

    如何创建倒排索索引举例

    创建倒排索引是一个关键的信息检索任务,让我们通过一个具体的示例来说明如何创建倒排索引。我们将考虑一个小型文档集合,其中包含三篇文档,然后创建一个简化的倒排索引。

    文档集合

    • 文档1: "This is a sample document."
    • 文档2: "A sample document is created."
    • 文档3: "Document creation is done."

    步骤

    1. 文本预处理:在预处理步骤中,我们进行以下操作:

      • 分词:将文档拆分为单词,同时去除标点符号和停用词。
      • 转小写:将所有单词转换为小写字母,以保持一致性。
      • 词干化:将单词还原为其基本形式。

      预处理后的文档如下:

      文档1:["sample", "document"] 文档2:["sample", "document", "created"] 文档3:["document", "creation", "done"]

    2. 构建倒排索引

      现在,我们将为每个单词构建倒排索引。倒排索引的数据结构通常包括词汇表和倒排列表。

      • 词汇表:词汇表包含所有不重复的单词,按字母顺序排序。

        词汇表:["a", "creation", "created", "document", "done", "is", "sample", "this"]

      • 倒排列表:对于每个单词,我们创建一个倒排列表,其中包含包含该单词的文档ID列表。

        示例倒排列表:

        • "a": [2]
        • "creation": [3]
        • "created": [2]
        • "document": [1, 2, 3]
        • "done": [3]
        • "is": [1, 2]
        • "sample": [1, 2]
        • "this": [1]
    3. 存储索引

      在实际应用中,倒排索引通常存储在磁盘上,以便随时访问。索引可以根据需要进行优化和压缩。

    4. 搜索

      当用户发起搜索查询时,查询单词会匹配词汇表中的条目。然后,可以检索倒排列表以查找包含查询单词的文档ID列表。这些文档ID可以用于检索和排序搜索结果。

    例如,如果用户搜索 "sample document",我们首先查找词汇表中的 "sample" 和 "document" 条目,然后检索它们的倒排列表。在这个示例中,两个单词都在文档1和文档2中出现,因此这两篇文档将作为搜索结果返回。

    这只是一个简化的示例,真实的倒排索引可以处理大规模文档集合,并包括更多的优化和数据结构。倒排索引是搜索引擎中的核心组成部分,它允许高效地检索文档和返回相关的搜索结果。

    查询倒排索索引举例分析

    查询倒排索引是搜索引擎的核心操作之一,让我们通过一个具体的示例来分析如何查询倒排索引。

    假设我们有以下的倒排索引:

    • 词汇表:

      • "apple"
      • "banana"
      • "cherry"
      • "date"
    • 倒排列表:

      • "apple": [1, 3, 4]
      • "banana": [2, 3]
      • "cherry": [1, 4]
      • "date": [2]

    这个倒排索引表示了四篇文档(文档ID 1, 2, 3, 4)中包含的单词和它们的文档关联。

    现在,让我们来查询倒排索引:假设用户发起了查询 "apple banana",希望找到包含这两个单词的文档。

    1. 分词和预处理:对查询进行分词和预处理,得到单词列表 ["apple", "banana"]。

    2. 查询词汇表:查询词汇表,查找每个查询词汇的倒排列表。

      • "apple" 对应的倒排列表是 [1, 3, 4]
      • "banana" 对应的倒排列表是 [2, 3]
    3. 合并倒排列表

      对查询结果进行合并。这里我们要找到同时包含 "apple" 和 "banana" 的文档,因此我们需要找到两个倒排列表的交集。

      • "apple" 的倒排列表:[1, 3, 4]
      • "banana" 的倒排列表:[2, 3]

      交集:[3]

      因此,文档ID 3 包含了同时包含 "apple" 和 "banana" 的文档。

    4. 返回结果

      返回文档ID 3 作为查询结果,指示文档3包含了查询中的两个单词。

    这个示例展示了如何查询倒排索引以查找包含多个查询词汇的文档。查询过程包括查询词汇表、合并倒排列表,并返回匹配的文档ID。在实际搜索引擎中,这个过程会针对大规模的文档集合和更复杂的查询进行优化和加速。

    三、检索进阶分析

    参考文章和技术

    极客时间-陈东,《检索技术核心 20 讲》

  • 相关阅读:
    knn算法详解
    古人道中秋 | 制作一个可拖动的月球
    中止一个或多个 Web 请求
    Python升级之路( Lv15 ) 并发编程三剑客: 进程, 线程与协程
    【Spring】bean的实例化
    Repo 使用详解
    Linux服务器系统
    Unity重启 --- 工具介绍部分 (面板与工具条)
    MyBatisPlus入门篇2 - 条件查询、查询投影、查询条件、id生成策略、多记录操作、逻辑删除
    【Web】标准文档流
  • 原文地址:https://blog.csdn.net/xiaofeng10330111/article/details/132866513