【问题标题】:Fast algorithm for searching for substrings in a string用于在字符串中搜索子字符串的快速算法
【发布时间】:2010-12-18 10:41:01
【问题描述】:

我想要一个高效的算法(或库),我可以在 Java 中使用它来搜索字符串中的子字符串。

我想做的是:

给定一个输入字符串 - INSTR:

“BCDEFGH”

还有一组候选字符串——CAND:

“AB”、“CDE”、“FG”、“H”、“IJ”

在 INSTR

中查找任何与子字符串匹配的 CAND 字符串

在本例中,我将匹配“CDE”、“FG”和“H”(但不匹配“AB”和“IJ”)

可能有数千个候选字符串(在 CAND 中),但更重要的是,我将进行数百万次此搜索,因此我需要它是 FAST。

我想使用 char 数组。此外,我对架构解决方案没有兴趣,例如分发搜索 - 只是在本地执行它的最有效的功能/算法。

此外,CAND 和 INSTR 中的所有字符串都相对较小(


更新我应该提到,CAND 字符串集在 INSTR 的所有值中是不变的。

更新我只需要知道有匹配 - 我不需要知道匹配是什么。

最终更新 由于实现简单,我选择尝试 AhoCorsick 和 Rabin-Karp。 因为我有可变长度的模式,所以我使用了一个修改过的 Rabin-Karp,它对每个模式的前 n 个字符进行哈希处理,其中 n 是最小模式的长度,N 是我的滚动子字符串搜索窗口的长度。 对于 Aho Corsick,我使用了 this

在我的测试中,我在两篇新闻报纸文章中搜索了 1000 种模式,平均经过 1000 次迭代等... 完成的标准化时间是:

AhoCorsick:1

拉宾卡普:1.8

朴素搜索(检查每个模式并使用 string.contains):50


*描述以下答案中提到的算法的一些资源:

http://www.seas.gwu.edu/~simhaweb/cs151/lectures/module5/module5.html

http://www.cs.princeton.edu/courses/archive/spr09/cos226/lectures/18SubstringSearch-2x2.pdf

http://www-igm.univ-mlv.fr/~lecroq/string/index.html*

【问题讨论】:

  • 输入字符串相对于候选字符串有多长?
  • 它们很短。输入字符串通常少于 40 个字符,候选字符串也是如此。
  • 但可能有数千个候选字符串 - 我想在许多输入字符串中重复执行此操作数百万次。
  • 鉴于细节,FSM 可能是您的最佳选择。
  • 很好奇你期望的输出。是知道找到了一个就足够了,还是您需要知道找到了哪些?您是否需要知道每个子字符串被找到了多少次?

标签: java algorithm string search


【解决方案1】:

【讨论】:

【解决方案2】:

将候选字符串集合转换为确定性有限状态自动机,然后在线性时间内遍历输入字符串。标准书籍中详细介绍了将单个字符串转换为 DFS 的内容。您可以通过首先构造一个非确定性自动机然后确定它来转换一组字符串。在最坏的情况下,这可能会在自动机大小的情况下造成指数爆炸,但之后的搜索速度很快;特别是如果目标字符串很长而候选字符串很短,这会很好地工作。

【讨论】:

  • 考虑到输入字符串和候选字符串都非常短,这是否相关?
  • @Joel 我认为这取决于您在上面写“我想在许多输入字符串中重复执行此操作”时的意思。 DFS 不依赖于输入字符串,因此如果候选字符串集在多个输入字符串中是恒定的,那么这相当于一个长输入字符串,因此该解决方案肯定是相关的。如果所有字符串都很短并且候选者每次都在变化,那么它可能不是最佳解决方案。
  • 这与上面建议的 Rabin-Karp 多模式搜索算法相比如何?考虑到大量可能的子字符串,以及相对较短的输入字符串长度,这似乎是一个不错的解决方案?
  • @Joel Rabin-Karp 绝对是另一个很好的解决方案,它只是具有不同的渐近行为。 DFS 解决方案具有最佳的匹配渐近复杂度,因为它仅在输入字符串的长度上是 O(n)。 Rabin-Karp 在复杂性方面有额外的术语。然而,从头开始实现更简单。
  • 请给出一个链接或书名,其中描述了如何将单个字符串转换为 DFS。我找不到它....
【解决方案3】:

这就是正则表达式的用途。如上所述,您需要有限状态自动机,但这正是标准正则表达式匹配器的实现方式。

在java中你可以这样写:

StringBuilder sb = new StringBuilder();
bool first = true;
for (String subStr : substrings) {
    if (first)
        first = false;
    else
        sb.append('|');
    sb.append(escape(subStr));
}
Pattern p = Pattern.compile(sb.toString());

escape 方法应该转义任何在正则表达式中具有特殊含义的字符。

【讨论】:

  • 我不能解释为什么它被否决了,但我可以说由于 Java 正则表达式的实现方式,这个正则表达式可能比单独搜索每个子字符串效率低。
  • 我喜欢这个解决方案并支持它。但是,我想指出两个潜在的问题: (1) 给定一千个左右的搜索字符串,模式编译器可能会爆炸。我担心内存使用会随着匹配表达式的复杂性呈指数增长。 (2) 我相信模式编译器构建的 FSM/DFS 有时会备份。如果是这样,那么其中一种严格向前移动的专用算法最终可能会更快。
  • 我并没有声称我的解决方案是完美的。不过,这可能就足够了。 YMMV。
  • 正则表达式执行“a|b|c|d”之类的模式的方式是从位置 0 开始,尝试所有选项,移动到位置 1,尝试所有选项,移动到位置 2,尝试所有选项等。在没有大的正则表达式的情况下,更快的方法或者我认为写起来很简单。在所有 INSTR 中搜索 CAND1,然后搜索 CAND2 等,永远不会更糟,而且通常会好得多。此外,这种搜索更容易优化。
  • @PSpeed:我无法想象任何真正的正则表达式库会像这样实现。无论如何,如果速度真的很重要,您可以尝试 [this library][0]。 [0]:brics.dk/automaton
【解决方案4】:

Rabin-Karp multiple pattern search 似乎是最快的。

【讨论】:

    【解决方案5】:

    您可能想研究Aho-Corasick algorithm 和相关算法。我不知道有什么库可以临时实现这个,但这是解决这个问题的经典方法。

    【讨论】:

    【解决方案6】:

    还要检查Boyer-Moore algorithm 是否有单字符串模式匹配。

    【讨论】:

      【解决方案7】:

      我们可以利用字符串的小尺寸(

      我们可以在一次哈希中哈希所有可能的 INSTR 子字符串,这将花费 O(n^2) 时间。然后不管 CAND 字符串的数量,查找将是 O(1)。值得为大量的 CAND 字符串。

      如果 INSTR 很大,那么我们可以构建一个后缀数组而不对其进行排序,这样顶部的项是最长的 (=N),底部的项是 INSTR 的最后一个字符。现在对于每个 CAND 字符串,只要 length(CAND)

      【讨论】:

      • 我对此有点模糊,所以我可能会在这里偏离基础,但哈希时间会是 O(n+1)(n/2) 而不是 O(n^2) 因为那应该有多少个不同的子字符串?
      • Big-O 忽略系数。将1 和2 从表达式中删除,剩下的O((n)(n)) 与O(n^2) 相同。
      【解决方案8】:

      另一种解决方案是为 INSTR 使用suffix array。
      由于 INSTR 很小,您可以使用冒泡排序对其进行排序。

      之后您可以在 O(logN) 时间内搜索特定的 CAND 字符串,
      其中 N = 长度(后缀数组)= 长度(INSTR)。

      【讨论】:

        【解决方案9】:

        Here 是 Java 中快速字符串搜索算法的一些实现。

        【讨论】:

        • 在哪里?忘记复制粘贴链接了吗?
        • 如果您点击“这里”文本,您将被重定向到带有算法的网站。
        【解决方案10】:
        import java.util.Scanner;
        
        public class StringMatch 
        {
            static int temp,i=0,j=0; static boolean flag=true,matcher=false;
        
            static String str=null,mstr=null;static char astr[],amstr[];
        
            static void getter(){
                Scanner sc = new Scanner(System.in);
                str = sc.nextLine();
                //String str="today is Monday"; 
                astr=str.toCharArray();
                 mstr = sc.nextLine();
                //String mstr="is"; 
                 amstr=mstr.toCharArray();
            }
        
            static void stringMatch(){
                while(i<astr.length){
                    if(astr[i]==amstr[j]){
                    while((j!=amstr.length)&&flag){temp=i;
                        if(astr[i]!=amstr[j]) {flag=false;matcher=false;}
                        else{matcher=true;}
                        i++;j++;
                        //System.out.println(i+"\t"+j);
                    }if(matcher==true)break;i=temp;}i++;j=0;flag=true;
        
                }
                if(matcher==true) {System.out.println("true");}
                else    {System.out.println("false");}
            }
        
            public static void main(String[] args) {
        
            StringMatch.getter();
            StringMatch.stringMatch();
        
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2013-01-20
          • 2013-01-06
          • 1970-01-01
          • 1970-01-01
          • 2011-03-01
          • 2020-04-09
          • 1970-01-01
          • 2016-09-18
          • 1970-01-01
          相关资源
          最近更新 更多