【问题标题】:DFA running time is not O(n) but O(nm)DFA 运行时间不是 O(n) 而是 O(nm)
【发布时间】:2016-12-23 02:47:10
【问题描述】:

如果您有一个包含 m 个节点的 DFA,并且您在一个包含 n 个字符的字符串上运行它,那么在每个步骤中,您不仅要测试从上一步继承的状态,还要再次测试 DFA 的第一个状态。所以在字符串中的 m 个字符之后(假设 m

举个例子,考虑 a{l}b 正则表达式(所有单词以 a 开头重复 l 次,后跟 a b),它的 DFA 有 m = l + 1 个节点。将其与带有 k>l 的字符串 a{k}b 进行匹配意味着您将遇到最坏的情况,即在字符串中的 l 个字符之后具有 (m - 1) 个活动状态。

我错过了什么?或者文献是否将实际实现仅仅关注于知道给定的完整字符串(即不是其中的一个子字符串)是否与正则表达式匹配的理论问题。

从我所在的位置运行 NFA 或 DFA 将花费 O(nm) 次(m 是 NFA 或 DFA 中的节点数和 n 个字符)。唯一的问题是 NFA 比 DFA 有更多的节点。

【问题讨论】:

    标签: algorithm big-o dfa nfa


    【解决方案1】:

    从历史上看,DFA 最初被定义为匹配整个字符串而不是搜索子字符串,这就是为什么文献通常谈论 DFA 的时间复杂度,即接收单个字符串然后返回整个字符串是否匹配或不。如果您有一个匹配整个字符串的 DFA,并且您想使用它来搜索子字符串,那么您实际上是多次运行 DFA,每个可能的起始位置一次,这就是您得到 O(mn) 的原因作为你的运行时而不是 O(n)。

    但是,如果您的目标是在某处匹配子字符串,则重新设计 DFA 可能会更好。例如,假设您想使用 DFA 匹配某个正则表达式 R。与其为 R 构建一个 DFA 并从每个可能的位置开始运行它,不如为正则表达式 Σ* R Σ* 构建一个 DFA。现在,如果输入的任何子字符串与 R 匹配,则整个字符串与 Σ* R Σ * 匹配,因此您只需在字符串上运行一次 DFA。这会将运行时间降低到 O(n),因为您只运行一次。

    【讨论】:

      【解决方案2】:

      如果您真的有一个 DFA,您将不会有多个活动状态。 DFA 被定义为只有一个活动状态。而且每个字符只能准确地导致下一个状态。

      如果你取这个属性,你从起始状态开始,消耗 n 个字符。在您检查的每个字符处: - 如果没有转换到非错误状态 => 不匹配 - 如果转换到非错误状态 => 继续

      最后检查您的当前状态是否为最终状态。如果是 => 成功,否则 => 不匹配。

      在我看来,NFA 需要 O(n*m),而 DFA 需要 O(n)。 DFA 性能不依赖于模式复杂度(节点数)。

      但我不知道您为什么接受了一个使用 DFA 而不是使用 DFA 进行字符串匹配的字符串搜索(实际上不是 O(n))的答案。但是,如果这是您的问题:从 DFA 派生的算法比搜索 Σ 做得更好,这将是 Knuth-Morris-Pratt(针对单一模式)和 Aho- Corasick(多种模式)。底层 DFA 被压缩,但两种算法共享一个属性,即它们在任何时候都为一个不具有多个状态的角色执行一次转换(如在 NFA 中)。

      【讨论】:

      • 我在正则表达式的上下文中查看这个,我没有想到将 Σ* 添加到 DFA 以便它可以找到子字符串匹配的技巧。这样做会得到 O(n),我天真地考虑必须为每个角色重新测试 DFA 的第一个状态。
      猜你喜欢
      • 1970-01-01
      • 2017-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多