Konwersja wyrażeń regularnych na (minimalne) NFA, które akceptują ten sam język, jest łatwa dzięki standardowym algorytmom, np . Algorytmowi Thompsona . Drugi kierunek wydaje się jednak bardziej nużący, a czasem wynikowe wyrażenia są nieuporządkowane.
Jakie są algorytmy przekształcania NFA w równoważne wyrażenia regularne? Czy są zalety dotyczące złożoności czasu lub wielkości wyniku?
To ma być pytanie referencyjne. Podaj ogólną opis swojej metody, a także nietrywialny przykład.
Odpowiedzi:
Istnieje kilka metod konwersji z automatów skończonych na wyrażenia regularne. Tutaj opiszę ten, którego zwykle uczy się w szkole, który jest bardzo wizualny. Uważam, że jest najczęściej używany w praktyce. Jednak napisanie algorytmu nie jest tak dobrym pomysłem.
Metoda usuwania stanu
Algorytm ten dotyczy obsługi wykresu automatu i dlatego nie jest zbyt odpowiedni dla algorytmów, ponieważ wymaga prymitywów grafowych, takich jak ... usunięcie stanu. Opiszę to za pomocą prymitywów wyższego poziomu.
Kluczowy pomysł
Chodzi o rozważenie wyrażeń regularnych na krawędziach, a następnie usunięcie stanów pośrednich przy zachowaniu spójności etykiet krawędzi.
Główny wzór można zobaczyć poniżej na rysunkach. Pierwszy ma etykiety między które są wyrażeniami regularnymi e , f , g , h , i, i chcemy usunąć q .p,q,r e,f,g,h,i q
Po usunięciu składamy razem (zachowując pozostałe krawędzie między p i r, ale nie jest to wyświetlane na tym):e,f,g,h,i p r
Przykład
Korzystając z tego samego przykładu, co w odpowiedzi Raphaela :
sukcesywnie usuwamy :q2
a następnie :q3
wtedy nadal musimy zastosować gwiazdkę do wyrażenia od do q 1 . W tym przypadku stan końcowy jest również początkowy, więc naprawdę wystarczy dodać gwiazdkę:q1 q1
Algorytm
L[i,j]
to wyrażenie regularne języka od do q j . Najpierw usuwamy wszystkie krawędzie:Teraz usunięcie stanu. Załóżmy, że chcemy usunąć stan :qk
Należy zauważyć, że zarówno z ołówkiem papieru i algorytmu należy uprościć wyrażenia jak∅ ε qi qk qj qk
star(ε)=ε
,e.ε=e
,∅+e=e
,∅.e=∅
(ręcznie po prostu nie pisać krawędź gdy nie lub nawet ε dla siebie pętli i zignorować, gdy istnieje brak przejścia między q i a q k lub q j i q k )Teraz, jak korzystać
remove(k)
? Nie powinieneś lekko usuwać stanów końcowych ani początkowych, w przeciwnym razie ominiesz część języka.Jeśli masz tylko jeden ostateczny stan i jeden stan początkowy q s następnie ostateczne wyrażenie jest:qf qs
Jeśli masz kilka stanów końcowych (lub nawet stanów początkowych), nie ma prostego sposobu połączenia tych stanów, oprócz zastosowania metody zamykania przechodniego. Zwykle nie jest to problem ręcznie, ale jest to niewygodne podczas pisania algorytmu. Znacznie prostszym obejściem jest wyliczenie wszystkich par i uruchomienie algorytmu na wykresie (już usuniętym przez stan), aby uzyskać wszystkie wyrażenia e s , f zakładając , że s jest jedynym stanem początkowym if jest jedynym stanem końcowym, następnie robi unii wszystkich e s , f .(s,f) es,f s f es,f
To oraz fakt, że modyfikuje języki bardziej dynamicznie niż pierwsza metoda, czyni go bardziej podatnym na błędy podczas programowania. Sugeruję użycie innej metody.
Cons
Algorytm zawiera wiele przypadków, na przykład wybór węzła, który należy usunąć, liczbę stanów końcowych na końcu, fakt, że stan końcowy może być również początkowy itp.
Zauważ, że teraz, gdy algorytm jest zapisany, przypomina to metodę zamykania przechodniego. Jedynie kontekst użytkowania jest inny. Nie polecam implementacji algorytmu, ale dobrym pomysłem jest użycie metody ręcznej.
źródło
ab
metoda
Najładniejszą metodą, jaką widziałem, jest ta, która wyraża automat jako układ równań (zwykłych) języków, które można rozwiązać. Jest to szczególnie miłe, ponieważ wydaje się przynosić bardziej zwięzłe wyrażenia niż inne metody.
Niech bez NFAA=(Q,Σ,δ,q0,F) przejścia. Dla każdego stanu q i utwórz równanieε qi
gdzie jest zbiorem stanów końcowych, a q i a → q j oznacza przejście od q i do q j oznaczonego a . Jeśli czytasz ∪ jako + lub ∣ (w zależności od definicji wyrażenia regularnego), zobaczysz, że jest to równanie wyrażeń regularnych.F qi→aqj qi qj a ∪ + ∣
Do rozwiązania systemu potrzebujesz asocjatywności i dystrybucji i ⋅ (konkatenacji łańcuchów), komutatywności ∪ i Lemmy Ardena ¹:∪ ⋅ ∪
Rozwiązaniem jest zbiór wyrażeń regularnych , po jednym dla każdego stanu q í . Q i opisuje dokładnie te słowa, które mogą być zaakceptowane przez A kiedy rozpoczęła się w q í ; dlatego Q 0 (jeśli q 0 jest stanem początkowym) jest pożądanym wyrażeniem.Qi qi Qi A qi Q0 q0
Przykład
Dla jasności oznaczamy zestawy singletonów według ich elementu, tj. . Ten przykład należy do Georga Zetzschego.a={a}
Rozważ to NFA:
[ źródło ]
Odpowiedni układ równań to:
Teraz podłącz trzecie równanie do drugiego:
Na ostatnim etapie stosujemy Arden lematu z , u = a , b i V = ( b ∪ a ) ⋅ Q 0 . Zauważ, że wszystkie trzy języki są regularne i ε ∉ U = { a b } , co pozwala nam zastosować lemat. Teraz wstawiamy ten wynik do pierwszego równania:L=Q1 U=ab V=(b∪aa)⋅Q0 ε∉U={ab}
Tak więc znaleźliśmy wyrażenie regularne dla języka akceptowanego przez powyższy automat, mianowicie
Zauważ, że jest dość zwięzły (porównaj z wynikiem innych metod), ale nie jest jednoznacznie określony; rozwiązanie układu równań z inną sekwencją manipulacji prowadzi do innych - równoważnych! - wyrażenia.
źródło
maybe_union/2
predykat może wymagać więcej pracy (zwłaszcza wrt eliminując wspólny przedrostek), aby uzyskać bardziej regularne wyrażenia regularne. Innym sposobem postrzegania tej metody jest zrozumienie jej jako tłumaczenia z wyrażenia regularnego na gramatykę liniowo-prawą, gdzie języki z ujednoliceniem przypominającym Prolog lub dopasowaniem wzorców podobnym do ML tworzą bardzo dobre przetworniki, więc nie są to tylko kartki algorytm :)Metoda algebraiczna Brzozowskiego
Jest to ta sama metoda, którą opisano w odpowiedzi Raphaela , ale z punktu widzenia algorytmu systematycznego, a następnie algorytmu. Okazuje się, że wdrożenie jest łatwe i naturalne, gdy wiesz, od czego zacząć. Może być również łatwiejsze ręczne, jeśli rysowanie wszystkich automatów jest z jakiegoś powodu niepraktyczne.
Pisząc algorytm, należy pamiętać, że równania muszą być zawsze liniowe, aby uzyskać dobrą abstrakcyjną reprezentację równań, o czym można zapomnieć, rozwiązując ręcznie.
Idea algorytmu
Nie opiszę, jak to działa, ponieważ dobrze to zrobiono w odpowiedzi Raphaela, którą sugeruję przeczytać wcześniej. Zamiast tego skupiam się na tym, w jakiej kolejności należy rozwiązywać równania bez wykonywania zbyt wielu dodatkowych obliczeń lub dodatkowych przypadków.
Począwszy od genialnego rozwiązania reguły Ardena do równania językowego X = A X ∪ B możemy uznać automat za zbiór równań formy:X=A∗B X=AX∪B
możemy rozwiązać ten problem przez indukcję na aktualizując tablice i , j oraz B : i , j odpowiednio. W kroku n mamy:n Ai,j Bi,j n
a reguła Ardena daje nam:
Algorytm
a następnie rozwiązanie:
końcowe wyrażenie to:
Realizacja
Nawet jeśli może się to wydawać układem równań, który wydaje się zbyt symboliczny dla algorytmu, ten doskonale nadaje się do implementacji.
Oto implementacja tego algorytmu w Ocaml(uszkodzony link) . Zauważ, że oprócz funkcjibrzozowski
wszystko służy do wydrukowania lub użycia w przykładzie Raphaela. Zauważ, że istnieje zaskakująco skuteczna funkcja uproszczenia wyrażeń regularnychsimple_re
.źródło
Metoda zamknięcia przechodniego
Ta metoda jest łatwa do zapisania w formie algorytmu, ale generuje absurdalnie duże wyrażenia regularne i jest niepraktyczna, jeśli robisz to ręcznie, głównie dlatego, że jest to zbyt systematyczne. Jest to jednak dobre i proste rozwiązanie dla algorytmu.
Kluczowy pomysł
Przykład
Użyjemy tego samego przykładu, co w odpowiedzi Rafaela . Na początku możesz używać tylko bezpośrednich przejść.
Algorytm
Inicjalizacja:
Przejściowe zamknięcie:
źródło