基于广义后缀树的解决方案
后缀树不仅适用于后缀匹配。以下是您可以执行的操作:
- 为哈希表中的每个条目构建一个通用后缀树。请注意,您必须将所有字符串转换为任意大小写才能忽略大小写。在构造过程中,用共享它的字符串集标记每个叶子(例如字符串
hazelnut和coconut将共享代表nut、ut和t的叶子)
- 从根开始:
- 使用子字符串 s 沿着树向下走(转换为第一步中选择的情况):您最终处于隐式状态(即处于边缘中间)或显式状态(你最终进入一个节点 N )。
- 如果您处于隐式状态,只需获取您所在边缘的目标节点,我们称该节点为 N。
- 计算你可以从N到达的所有叶子的字符串集合的并集:你得到一组字符串S
-
S 是哈希表中具有子字符串 s 的所有字符串的集合
复杂性分析
让K 为表中的字符串数。令 Li 为字符串 Si 的各自长度,令 L = ∑ L我。树的构造将是O(L)。
走到节点N是O(length(s))。
现在更棘手的部分开始了。列出一个节点可到达的所有叶子不是线性的,但不会太麻烦。
令Lmax = max(Li),那么你可以步行最多到达每一片叶子> Lmax 个节点,更准确地说,如果你从之前定义的 N 个节点开始,你会到达 的每个子叶子N 最多 L(s) = Lmax - 长度(s) 步。
从N开始的子树也具有广义后缀树的结构。它表示最多 K 个长度最多为 L(s) 的字符串。这些字符串中的任何一个都最多有 L(s) 个叶子。所以迭代它们最多是 O(K.L(s)2).
在每个这样的叶子中计算字符串集合的并集最多O([K.L(s)]2)。 (实际上它会更接近于 O(KL(s)2) 因为如果每个叶子都在其所有原始 K 字符串集,则以 N 为根的子树中只有 L(s) 个叶子)。
这导致最坏情况的总复杂度为:
O(L + 长度(s) + [K.L(s)]2)
但实际使用复杂度将更接近:
O(L + length(s) + K.[L(s)]2)
标准方法(遍历每个字符串并在每个字符串中搜索s)是:
O(∑(Li + length(s))) = O(L + K.length(s))
但是等等……我们总是在寻找子字符串s!所以 KMP 算法 的预处理可以只进行一次......这降低了这种方法的复杂性:
O(L + 长度)
但是,为了从这种优化中受益,您必须自己编写而不是使用标准实现...
结论
假设您只需要在地图上测试一个字符串 s,那么简单的解决方案易于实现、易于理解,并且其整体复杂性不仅 优于基于后缀树的方法,它是最佳的。所以你可以自信地坚持下去。
但是,如果你必须测试大量 Ks 个字符串 sj,那么后缀树方法可能会更好,因为它的整体复杂度最多为:
O(L + Ks.(max(length(sj)) + [K.max(L(sj)]2))
而 KMP 方法将导致以下总体复杂性:
O(Ks.L + ∑(长度(sj))) = O(Ks.[ L + max(长度(sj))])
还请注意,由于后缀树是一种树状结构,如果设计不当,内存访问和分配就会发挥作用,严重影响运行时间。
如果你愿意,我可以举一个例子(在 C++ 中,但它仍然可以说明问题)。