我已经实现了自己的AVL树,我将它用作字典。我想知道,什么是计算以字符串开头的所有单词的最快方法。
例如:
string prefix = "fa";
output: 4
我已经让它在O(n)工作但是,我听说它可以更快地完成。 我当然可以在节点中保存其他信息,例如下面的节点和其他类似的东西。
答案 0 :(得分:1)
可以修改AVL树,以便每个节点也知道它的“索引” 1 (如果集合是排序数组,索引就是元素编号)。
你现在所要做的就是:
"FA"
,获取最接近较大(或等于)的索引i1
树中的元素"FB"
,获取树中最近但较小元素的索引i2
。i1
和i2
之间差异的元素数量(区分在1中找到“FA”的情况和不在其中的情况)。 1,2 O(logn)
- 3都是常数,因此总复杂度为O(logn)
(实际上O(logn * |S|)
,因为每个比较都是O(| S |)本身,你有O(logn)
比较)。
(1)通过让每个节点“记住”它拥有多少儿子来完成,你可以使用这些信息来最终提取索引。
答案 1 :(得分:1)
如果要在保持相同的渐近时间边界的同时尽可能地减少内存占用,则每个节点只需一个整数即可,并且仍然可以达到O(log n)
时间(假设是恒定时间密钥比较)。 / p>
将每个节点存储为其子树的大小。这可以在树修改期间轻松更新。
要查找给定范围的键数:
给定前缀的范围包含具有前缀的所有元素。重要的是要注意具有给定前缀的字符串集是连续的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
结尾的前缀开头。例如,以下是单词abbaba
和abbacba
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;