[英]Algorithm for extracting a language from DFA
我正在嘗試找到一種從DFA中提取語言的算法。 如果該語言是無限的,那么我只需要報告: maxcount
<= 1000個字符串。 對報告的順序沒有限制。
我試過:(我不知道如何編寫算法,所以我只是用文字解釋我做了什么)
遞歸算法 - 對於從一個狀態到下一個狀態/狀態的每個轉換,保存轉換字符,然后對每個轉換進行遞歸調用,如果轉換到接受狀態然后報告。如果我們已達到“死胡同”然后有注意到。
假設開始狀態是state 1
並且有兩個轉換到: state 2
和state 3
具有轉換字符'a'
, 'b'
。 然后是從state 3
到state 4
的轉換,轉換字符'c'
,只有state 4
是接受狀態。
從state 1
到state 2
將保存'a'
作為轉換字符,但由於state 2
是死胡同,因此它將注意到,因為它不是接受狀態,所以有記錄報告。
從state 1
到state 3
將保存'b'
然后從state 3
轉到state 4
將保存'c'
以便我們總共具有"bc"
並且因為state 4
沒有任何轉換,所以遞歸結束。 我們報告"bc"
因為state 4
是接受狀態
如果我有一個帶循環的DFA - 那么為了不在該循環中“卡住”,我確保“如果”還有另一個可能的轉換,那么我們將總是選擇那個轉換,而不是我們上次做出的轉換/我們處於那種狀態的時間(我們在哪些狀態下進行過渡的記憶)
該算法適用於小型DFA,但它會在更大的DFA上產生堆棧溢出:s(想象20從state 1
轉換到state 2
和從state 2
轉換到state 3
,依此類推)。
任何人都可以推薦更高效的算法嗎?
交替:
我會通過廣泛優先搜索DFA來執行此操作,這將生成按長度排序的字符串。
定義一個由狀態和字符串組成的對象(這里有一個更節省內存的解決方案,但我認為如果你只需要生成1000個字符串,這樣就可以了)。 然后創建此類對象的工作隊列。 使用單個對象初始化工作隊列,該對象的狀態為起始狀態且其字符串為空。
現在重復以下三個步驟,直到找到maxcount
字符串或隊列變為空:
刪除隊列中的第一個對象。
如果其狀態是接受狀態,則輸出其字符串。
對於每個可能的out轉換(由一個字符和一個新狀態組成),將一個新對象添加到隊列的末尾,該對象具有轉換的狀態以及對象的字符串與轉換字符的串聯。
如果使用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]
注意 :我猜您可以將此問題建模為查找開始狀態和所有接受狀態之間的所有路徑。 所以,我相信這可以通過深度優先搜索圖來完成。 深度優先搜索將找到兩個節點之間的所有非循環路徑。
如果您熟悉深度優先搜索(DFS)算法,這是一個圖搜索/遍歷算法可以幫助您解決問題。 讓我舉一個非常簡單的例子。
給定有向圖,源頂點'和目標頂點'd',打印從給定's'到'd'的所有路徑。 請考慮以下有向圖。 設s為2,d為3.有4個不同的路徑,從2到3。
我們的想法是對給定的有向圖進行Depth First Traversal
。 從源開始遍歷。 繼續將訪問過的頂點存儲在數組中,如path[]
。 如果我們到達目標頂點,則打印path[]
內容。 重要的是將path[]
當前頂點標記為也訪問,以便遍歷不會進入循環。
示例代碼 :您可以在此處找到一個非常簡單的Java
代碼,以查找圖中兩個節點之間的所有路徑。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.