【问题标题】:Is there an efficient algorithm to extract from an array of string only those which contain a specific substring?是否有一种有效的算法可以从字符串数组中仅提取包含特定子字符串的字符串?
【发布时间】:2019-12-23 20:23:46
【问题描述】:

想象有一个大的字符串 S 数组。从该数组中我只需要获取那些包含特定子字符串的字符串。例如,如果我的数组是 String s [] = {"hello world", "back to hell", "say hello world"}; 我的关键字是“你好”,那么它应该返回我的第一个和最后一个元素。 我尝试使用 KMP 和 Boyer-Moor 算法检查数组中的每个字符串是否包含子字符串,但这需要太多时间。 然后我了解了 Aho-Corasick 算法。我仍在查找它,但似乎它需要一组子字符串和一个大字符串来匹配,而我想要的恰恰相反。 因此,我一直在寻找有关如何为我的目的修改 Aho-Corasick 算法的建议,或其他实现这些目的的方法。将不胜感激任何建议。

【问题讨论】:

  • 这是您将执行一次还是多次的操作?换句话说,是否可以进行昂贵的操作来构建一个可以多次用于此搜索的数据结构?
  • 会做很多次,所以是的,没关系。

标签: arrays string algorithm aho-corasick


【解决方案1】:

使用Ukkonen 算法或this source(PDF) 中建议的算法构建后缀树:

McCreight 的算法可以很容易地适应为集合 S={s1, s2, . . . , s_k}总长度为 N 的字符串在 O(N) 时间内...

然后使用创建的后缀树来搜索给定的模式。问题是在后缀树T中找到所有出现的模式P(长度m)。根据上面的来源:

模式匹配问题可以在最优O(m+k)时间...,其中k是P在T中出现的次数

请注意,文本的长度(或数组中字符串的数量)不会影响搜索效率。因此,您可以支付一次构建后缀树的费用,然后多次使用它来有效地搜索短模式字符串。

编辑:如果您赶时间并且不介意一点额外的时间复杂度,您可以使用 this approach(PDF) 在 O(n*log ^2(n)) 用一小段代码。以下是这种方法的核心思想:

该算法主要是基于维护字符串后缀的顺序,按照它们的 2^k 长前缀排序。

这是从上述来源复制的伪代码:

n ←length(T) 
  for i←0 : n – 1
    P(0, i)← position of T(i) in the ordered array of T‘s characters 
cnt ← 1 
for k←1 : [log2n] (ceil)
  for i←0 : n – 1
    L(i)← (P(k – 1, i), P(k – 1, i + cnt), i)             
    sort L
    compute P(k, i) , i = 0, n - 1 
    cnt←2 * cnt

运行此代码后,P 将包含后缀数组。使用这种方法进行搜索也很简单:

由于后缀数组提供了 T 后缀的顺序,因此搜索一个 字符串 P 到 T 很容易通过二分查找完成。由于比较 在 O(|P|) 中完成

【讨论】:

  • 我的答案是独立于这个编写的,并且基本上是相同的答案,除了不是外部链接和“......可以很容易地适应......”我提供了概念代码的运行证明。
  • @btilly 我认为外部链接和一般解释足以回答这种情况下的问题,因为 OP 也没有详细说明,也没有发布任何代码示例。话虽如此,包含代码的更详细的答案没有错。
  • 由于链接失效,不鼓励将外部链接作为答案。此外,“可以很容易地适应”有点误导。如果您对原始算法有深入的了解,则适配在概念上更加复杂且直接。我不希望大多数程序员能够很好地理解算法来进行适应。
  • @btilly 无论如何这都不是 link-only 答案。已经给出了核心思想、时间复杂度和算法的名称,因此 OP 可以进行更多搜索并找到最适合他们的方法。我同意你关于“可以很容易地适应”的观点。这是源中引用的一部分,而不是我自己的观点。
  • @btilly 恕我直言,有很多关于构建和使用后缀树的教程和指南,我认为没有必要在回答一般问题时重现这些算法。但是,我发现这样做的答案没有问题。
猜你喜欢
  • 2020-11-26
  • 2020-07-16
  • 1970-01-01
  • 2023-03-24
  • 2022-01-13
  • 2016-12-12
相关资源
最近更新 更多