【问题标题】:Algorithm for extracting a language from DFA从 DFA 中提取语言的算法
【发布时间】:2017-04-14 02:08:15
【问题描述】:

我正在尝试寻找一种算法来从 DFA 中提取语言。如果该语言是无限的,那么我只需要报告:maxcount

我试过了:(我不会写算法,所以我只是用文字解释我做了什么)

一种递归算法 - 对于从一个状态到下一个/多个状态的每个转换,保存转换字符,然后为每个转换进行递归调用,如果该转换是到接受状态,则报告。如果我们已经达到“死胡同”然后有注意递归。

假设开始状态是state 1,并且有两个转换到:state 2state 3,转换字符分别为'a','b'。然后有一个从state 3state 4的转换,转换字符为'c',只有state 4是一个接受状态。

state 1state 2 将保存'a' 作为转换字符,但由于state 2 是一个死胡同,因此将注意递归,因为它不是一个接受状态,所以需要注意报告。

state 1state 3 将保存'b',然后从state 3state 4 将保存'c',所以我们总共有"bc",因为state 4 没有有递归结束的任何转换。我们报告"bc" 因为state 4 是一个接受状态


如果我有带循环的 DFA - 那么为了不“卡在”该循环中,我已确保“如果”存在另一个可能的转换,那么我们将始终选择该转换而不是我们所做的转换上一次/次我们在那个状态下的位置(一种我们在哪个状态下进行转换的记忆)

该算法适用于小型 DFA,但它会在更大的 DFA:s 上产生堆栈溢出(想象从 state 1state 2 的 20 次转换和从 state 2state 3 的 30 次转换等)。

谁能推荐更高效的算法?

【问题讨论】:

  • 您熟悉任何图算法吗?图表搜索/连通性算法在这里很有用。
  • 你的算法是这样的:在 DFA 中查找开始和接受状态之间的所有路径吗?
  • @djechlin 我不是,但如果您能详细说明可以帮助我的特定算法,我会很高兴
  • 修改后的 Dijkstra 算法或 DFS-with-trace,您接受在一个循环中连接的节点最多重新访问节点/状态 K 次(例如 `(abc)*`)。从 K=1 开始,得到所有的产生式,如果不够,让 K=2 等等。
  • 只需查找图搜索或连接算法。

标签: java algorithm


【解决方案1】:

如果您使用 BFS,则周期无关紧要。这个想法是定义包含当前状态和指向前驱节点的指针的搜索节点。因此,当您访问一个接受状态的节点时,您可以向后跟踪先前的指针以确定接受的字符串(反向)。事实证明,如果搜索节点还包含导致从前一个节点状态转换到当前节点状态的字符,那么它是优雅的。

这是 Java 中的一种方法:

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

class Experimental {
  // DFA state with its transitions, possibly accepting.
  static class State {
    final Map<Character, State> transitions = new HashMap<>();
    final boolean accept;    
    State(boolean accept) {
      this.accept = accept;
    }
  }

  // A little DFA.
  static final State s0 = new State(false);
  static final State s1 = new State(false);
  static final State s2 = new State(true);
  static final State s3 = new State(true);
  static {
    s0.transitions.put('a', s1);
    s0.transitions.put('b', s2);
    s0.transitions.put('c', s3);
    s1.transitions.put('d', s3);
    s2.transitions.put('e', s0);
    s2.transitions.put('f', s1);
  }

  // An enumerator of strings accepted by the DFA in order of length.
  static class Enumerator {
    static class Node {
      final Node prev;
      final char prevCh;
      final State state;
      Node(State start) {
        this(null, Character.MIN_VALUE, start);
      } 
      Node(Node prev, char ch, State state) {
        this.prev = prev;
        this.prevCh = ch;
        this.state = state;
      }
    }

    final Deque<Node> queue = new ArrayDeque<>();
    final List<String> output = new ArrayList<>();
    final State start;

    Enumerator(State start) {
      this.start = start;
    }

    Enumerator enumerate(int outputLimit) {
      queue.clear();
      output.clear();
      // Enqueue a search node for the start state.
      queue.add(new Node(start));
      while (!queue.isEmpty() && output.size() < outputLimit) {
        Node current = queue.pollFirst();
        if (current.state.accept) {
          // Follow prev pointers to build the accepted string.
          StringBuilder sb = new StringBuilder();
          for (Node p = current; p.prev != null; p = p.prev) {
            sb.append(p.prevCh);
          }
          output.add(sb.reverse().toString());
        }
        // Enqueue nodes for the successors of current state.
        for (Entry<Character, State> transition : current.state.transitions.entrySet()) {
          queue.addLast(new Node(current, transition.getKey(), transition.getValue()));
        }
      }
      return this;
    }
  }

  public static void main(String[] args) {
    System.out.println(new Enumerator(s0).enumerate(20).output);
  }
}

输出:

[b, c, ad, beb, bec, bfd, bead, bebeb, bebec, bebfd, bebead, bebebeb, bebebec, bebebfd, bebebead, bebebebeb, bebebebec, bebebebfd, bebebebead, bebebebebeb]

【讨论】:

    【解决方案2】:

    我会在 DFA 上进行广度优先搜索,这将产生按长度排序的字符串。

    定义一个由状态和字符串组成的对象(这里有一个更节省内存的解决方案,但我认为如果你只需要产生1000个字符串就可以了)。然后创建此类对象的工作队列。使用状态为起始状态且字符串为空的单个对象初始化工作队列。

    现在重复以下三个步骤,直到找到maxcount 字符串或队列变为空:

    1. 移除队列中的第一个对象。

    2. 如果其状态为接受状态,则输出其字符串。

    3. 对于每个可能的输出转换(由一个字符和一个新状态组成),在队列末尾添加一个带有转换状态的新对象以及对象字符串与转换字符的连接。

    【讨论】:

    • 这里的字符串是什么意思? op 想通过遍历 DFA 图来构造语法规则。你是这个意思吗?
    • @wasi:我不相信这就是 OP 想要构建的;字符串一词直接来自问题(通常我会说“句子”和“令牌”而不是“字符串”和“字符”,但算法是相同的。)
    【解决方案3】:
    1. 运行循环检测算法。
    2. 如果没有循环,则运行任何路径查找算法(BFS、DFS、Dijkstra、A* 等)以列出从开始到结束的所有路径。
    3. 如果有循环运行寻路寻找从起始节点到循环的路径。然后输出(循环的开始节点)+(从连接节点开始的循环)* N for N = 1,2,3,...,1000。

    交替:

    1. 转换为正则表达式。
    2. 从正则表达式生成单词。

    【讨论】:

    • 您为什么需要担心周期?一个可能的答案是“因为如果存在循环,DFS 将永远不会终止”,这是真的,但 DFS 并不是剥树皮的唯一方法。
    • @rici 这个问题有很多答案。将因素挖掘到易于使用 Google 搜索的组件中。
    • google 是否可以帮助您解决“如果您找到的循环没有接受状态怎么办?”的问题? :-) 当然,修复算法相当简单,尽管它不会那么整洁。
    【解决方案4】:

    注意:我猜您可以将此问题建模为查找开始状态和所有接受状态之间的所有路径。所以,我相信这可以通过对图的深度优先搜索来完成。深度优先搜索将找到两个节点之间的所有非循环路径。

    如果您熟悉 Depth First Search (DFS) 算法,它是一种图搜索/遍历算法,可能会对您的问题有所帮助。让我举一个非常简单的例子。

    给定一个有向图,一个源顶点“s”和一个目标顶点“d”,打印从给定“s”到“d”的所有路径。考虑以下有向图。设 s 为 2,d 为 3。从 2 到 3 有 4 条不同的路径。

    这个想法是对给定的有向图做Depth First Traversal。从源头开始遍历。继续将访问过的顶点存储在一个数组中,比如path[]。如果我们到达目标顶点,则打印path[] 的内容。 重要的事情是将path[]中的当前顶点也标记为已访问,这样遍历就不会进入循环。

    示例代码:您可以在Java here 中找到一个非常简单的代码来查找图中两个节点之间的所有路径。

    【讨论】:

    • 对于典型的 DFA,不涉及状态重复的句子集的大小很可能小于 1000,因此该算法似乎不太可能将返回足够的字符串来满足问题约束。
    • 我没明白你的意思。您是说 DFS 无法通过查找图的两个节点之间的路径来生成语法规则?
    • 我发现您如何使用遍历 DFA 来生成语法并不明显,尽管我可以看到如何生成与 DFA 匹配的句子。不过,正如我对 djechlin 所说:为什么您认为循环甚至包含接受状态? (简单例子:正则表达式ab*a对应的DFA)
    猜你喜欢
    • 2017-06-25
    • 1970-01-01
    • 2012-02-16
    • 2017-03-13
    • 2012-07-18
    • 2014-05-18
    • 2013-09-07
    • 2017-09-01
    • 2018-09-03
    相关资源
    最近更新 更多