查找单词计数的最快方法,以指定的字符串开头(单词存储在AVL树中)

时间:2012-12-19 13:04:38

标签: algorithm avl-tree

我已经实现了自己的AVL树,我将它用作字典。我想知道,什么是计算以字符串开头的所有单词的最快方法。

例如:

string prefix = "fa";

enter image description here

output: 4

我已经让它在O(n)工作但是,我听说它可以更快地完成。 我当然可以在节点中保存其他信息,例如下面的节点和其他类似的东西。

3 个答案:

答案 0 :(得分:1)

可以修改AVL树,以便每个节点也知道它的“索引” 1 (如果集合是排序数组,索引就是元素编号)。

你现在所要​​做的就是:

  1. 搜索"FA",获取最接近较大(或等于)的索引i1 树中的元素
  2. 搜索"FB",获取树中最近但较小元素的索引i2
  3. 查找i1i2之间差异的元素数量(区分在1中找到“FA”的情况和不在其中的情况)。
  4. 1,2 O(logn) - 3都是常数,因此总复杂度为O(logn)(实际上O(logn * |S|),因为每个比较都是O(| S |)本身,你有O(logn)比较)。


    (1)通过让每个节点“记住”它拥有多少儿子来完成,你可以使用这些信息来最终提取索引。

答案 1 :(得分:1)

如果要在保持相同的渐近时间边界的同时尽可能地减少内存占用,则每个节点只需一个整数即可,并且仍然可以达到O(log n)时间(假设是恒定时间密钥比较)。 / p>

将每个节点存储为其子树的大小。这可以在树修改期间轻松更新。

要查找给定范围的键数:

  • 找到此范围内的顶部元素。也就是说,该范围内但不包含其祖先的唯一节点是。将元素称为“顶部”。
  • 如果不存在此类元素,请返回0
  • 初始化总和= 1(代表顶部)。
  • 在“top”的左子树中找到范围的开头:
    • 如果从节点向左下移,请将其整个右子树的大小添加到总和中,然后添加一个。
    • 如果向右下方,则不添加任何内容。
  • 在“top”的右子树中找到范围的结尾:
    • 如果您从节点下降,请将其整个左子树的大小添加到总和中,然后添加一个。
    • 如果您向左下方,则不添加任何内容。
  • 归还总和。

给定前缀的范围包含具有前缀的所有元素。重要的是要注意具有给定前缀的字符串集是连续的w.r.t.它的排序顺序 - 也就是说,它确实是一个范围。

前缀范围的开头是前缀本身之前的位置。

前缀范围的结尾是在此字典之后的字典顺序的第一个不相交前缀之前的位置(FA => FB; FZ=>GA当仅A-Z时在字母表中。)

Unicode通过引入可能实际上不会出现在文本中的“顶部”字符来简化这一点,并比较其他所有字符。也就是end = prefix + "\uFFFF"

答案 2 :(得分:0)

AVL-tree不是实现您想要的正确数据结构。还有一种称为基数树的数据结构,它可以回答O(lg N)复杂度中的前缀计数查询。基数树中的每个节点n具有0到26个子节点。它还有一个辅助变量prefix_count,它告诉我们基数树中有多少个单词以n结尾的前缀开头。例如,以下是单词abbabaabbacba

的基数树
        X <-- root node: it has no value
        |
        a <-- prefix count: 2
        |
        b <-- prefix count: 2
        |
        b <-- prefix count: 2
        |
        a <-- prefix count: 2
       / \
      b   c <-- prefix count: 1, 1
      |   |
      a   b <-- prefix count: 1, 1
          |
          a <-- prefix count: 1

因此,要检查树中有多少单词以前缀ab开头,您只需按照节点a --> b,然后返回此节点的前缀计数。如果找不到给定的前缀,则返回0。

实现技巧:每个节点应保留26个字符的数组,以提高所有操作的复杂性。

在psevdocode中实施:

struct node :
  let child -> array of 26 characters;
  let pc -> integer prefix counter;

struct radix-tree :
  node array[ MAXN ];
  let size -> integer size of the trie

init( rt ) :
  size := 1; // add the root

insert( rt, x ) :
  cn := 1; // root
  foreach i in x :
    if rt.array[ cn ].child[ i ] = null : // node doesn't exist
      rt.array[ cn ].child[ i ] := ++rt.size;
      cn := rt.size;
    else : // node exists
      cn := rt.array[ cn ].child[ i ];
    tr.array[ cn ].pc += 1;

prefix_count函数的实现与插入过程类似。

prefix-count( rt, x ) :
  cn := 1; // root
  foreach i in x :
    if rt.array[ cn ].child[ i ] = null :
      return 0; // 0 prefixes found
    else :
      cn := rt.array[ cn ].child[ i ];
  return rt.array[ cn ].pc;