我已经实现了自己的 AVL 树,并将其用作字典。我想知道,计算所有以某个字符串开头的单词的最快方法是什么。
例如:
string prefix = "fa";
output: 4
我已经让它在 O(n) 中工作,但是,我听说它可以做得更快。我当然可以在节点中保存额外的信息,比如下面的节点和其他类似的东西。
如果您想更改数据结构,则可以从trie获得卓越的性能。如果 trie 包含静态数据,您可以通过使用子树计数大小(使用动态编程生成)注释分支来获得更好的性能。
例如对于[竖琴,帽子,嗨]
h(3)
a(2) i()
r(1) t()
p()
可以修改 AVL 树,因此每个节点也将知道其“索引” 1(如果集合是排序数组,则索引是元素编号)。
您现在要做的就是:
"FA"
,获取树i1
中最近但更大(或等于)元素的索引"FB"
,获取树i2
中最接近但更小的元素的索引。i1
找出和之间有差异的元素数i2
(区分在 1 中找到“FA”的情况和不在 1 中的情况)。1,2 都是O(logn)
- 3 是常数,因此总复杂度是O(logn)
(实际上O(logn * |S|)
,因为每个比较都是 O(|S|) 本身,并且你有O(logn)
比较)。
(1) 它是通过让每个节点“记住”它有多少个儿子来完成的,你可以使用这些信息来最终提取索引。
如果您想在保持相同的渐近时间界限的同时尽可能减少内存占用,您可以为每个节点使用一个整数并仍然达到O(log n)
时间(假设恒定时间键比较)。
与每个节点一起存储其子树的大小。这可以在树修改期间轻松更新。
要查找给定范围内的键数:
给定前缀的范围包含所有具有该前缀的元素。重要的是要注意,具有给定前缀的字符串集在其排序顺序上是连续的——也就是说,它确实是一个范围。
前缀范围的开始是前缀本身之前的位置。
前缀范围的结尾是在字典序上第一个不相交前缀之前的位置(FA
=> FB
;FZ=>GA
当仅A-Z
在字母表中时)。
Unicode 通过引入一个可能实际上不会出现在文本中的“顶部”字符来简化这一点,并在所有其他字符之上进行比较。即,end = prefix + "\uFFFF"
。
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
因此,要检查树中有多少单词以前缀 example 开头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;