【问题标题】:Data structure for range query范围查询的数据结构
【发布时间】:2012-10-04 04:59:10
【问题描述】:

我最近被问到一个关于以下问题的编码问题。 我有一些解决这个问题的方法,但我不太确定这些方法是否最有效。


问题:

编写一个程序来跟踪一组文本范围。起点和终点都是字符串。

Text range example : [AbA-Ef]
 Aa would fall before this range
 AB would fall inside this range
 etc.

字符串比较就像 'A'

我们需要在这个范围内支持以下操作

  • 添加范围 - 如果适用,这应该合并范围
  • 删除范围 - 从跟踪范围中删除范围并重新计算范围
  • 查询范围 - 给定一个字符,函数应该返回它是否属于任何跟踪范围。

请注意,跟踪范围可以是不连续的。


我的解决方案:

我想出了两种方法。

  1. 将范围存储为双向链表或
  2. 将范围存储为某种平衡树,叶节点具有实际数据,并且它们以链表的形式相互连接。

您认为这个解决方案是否足够好,或者您可以想出更好的方法来做到这一点,以便这三个 API 为您提供最佳性能?

【问题讨论】:

  • 某事是否有效完全取决于问题范围。在这种情况下,会有多少查询?
  • 这听起来很像家庭作业......如果你能举一些例子也会有所帮助,正如我所说的问题不是很清楚。不过,作为一般建议,请先编写一个蛮力,缓慢但有效的实施。这将成为你的考验。查看测试慢的地方,然后修复它。
  • 这不是作业问题。正如问题中已经提到的,这个问题是在面试编码测试中提出的。

标签: algorithm data-structures


【解决方案1】:

您可能正在寻找 interval tree

将数据结构与您的自定义比较器一起使用以指示“范围内的内容”,您将能够有效地执行所需的操作。

注意,区间树实际上是实现您的第二个想法的有效方法 (Store ranges as a some sort of balanced tree)

【讨论】:

    【解决方案2】:

    我不清楚“删除范围”操作应该做什么。是吗?

    • 删除之前插入的范围,并重新计算剩余范围的合并?

    • 停止跟踪已删除的范围,无论其部分已添加多少次。

    这在算法上并没有太大的区别;这只是簿记。但重要的是要澄清。另外,范围是封闭的还是半开放的? (另一个不影响算法但影响实现的细节)。

    解决这个问题的基本方法是将跟踪集合并到一个不相交(不重叠)范围的排序列表中;作为向量或二叉搜索树,或者基本上任何支持 O(log n) 搜索的结构。

    一种方法是将每个不相交范围的两个端点放入数据结构中。要确定目标值是否在范围内,请查找大于目标的最小端点的索引。如果索引是奇数,则目标在某个范围内;甚至意味着它在外面。

    或者,通过起点索引所有不相交的范围;通过搜索不大于目标的最大起点找到目标,然后将目标与关联的终点进行比较。

    我通常对排序向量使用第一种方法,如果 (a) 空间利用率很重要并且 (b) 插入和合并相对较少,则这种方法是合理的。对于二叉搜索树,我选择第二种方法。但它们仅在细节和常数上有所不同。

    合并和删除并不难,但有很多令人讨厌的情况。您首先找到与要插入/删除的范围端点相对应的范围(使用标准查找操作),删除两者之间的所有范围,然后调整端点以纠正部分重叠的范围。虽然查找操作总是 O(log n),但树/向量操作是 o(n)(如果插入/删除的范围很大,无论如何)。

    【讨论】:

    • 您好,感谢您的回答。删除范围基本上应该重新计算范围。例如。如果我们从 [A-G] 中删除 [C-D] 那么它应该给出两个范围 -> [A-B] 和 [E-G]
    • 这是最简单的情况;那么,我的回答基本成立。不过,您应该澄清您是在谈论字符串还是字符:您的第一个语句是字符串范围。还是您的意思是 [A-G] 表示“所有以 A 和 G 之间的字母开头的字符串,无论多长时间”? (因此“Gz”是范围的一部分。)同样,这只是一个实现细节。更有趣的案例:我正在跟踪 [A-C][E-F][H-K] 并删除 [B-I]。现在我只剩下 [A][J-K]。假设我插入 [B-I]。现在我得到了[A-K]。因此,我在插入/删除范围内的声明可以被删除。
    【解决方案3】:

    大多数语言,包括 Java 和 C++,都有某种有序映射或有序集合,您既可以在其中查找一个值,也可以找到一个值之后的下一个值或值之前的第一个值。您可以将其用作构建块 - 如果它包含一组不相交的范围,那么它将具有范围的最小元素,然后是范围的最大元素,然后是范围的最小元素,然后是范围的最大元素范围等。添加范围时,您可以检查是否保留了此属性。如果没有,您需要合并范围。同样,您希望在删除时保留它。然后,您可以通过查看查询点之前是否有最小元素和之后是否有最大元素来进行查询。

    如果您想从头开始创建自己的数据结构,我会考虑某种 radix trie 结构,因为这样可以避免进行大量重复的字符串比较。

    【讨论】:

      【解决方案4】:

      我认为您会选择 B+ 树,这与您提到的第二种方法相同。

      以下是B+树的一些属性:

      1. 所有数据都存储在叶节点中。
      2. 每片叶子都处于同一水平。
      3. 所有叶节点都有到其他叶节点的链接。

      这里有几个应用B+树:

      1. 它减少了在树中查找元素所需的 I/O 操作次数。
      2. 常用于数据库索引的实现。
      3. B+ 树的主要价值在于存储数据以便在面向块的存储环境(尤其是文件系统)中进行高效检索。
      4. NTFS 使用 B+ 树进行目录索引。

      基本上它有助于范围查询查找,最大限度地减少树遍历。

      【讨论】:

        猜你喜欢
        • 2013-09-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多