표준 알고리즘 (예 : Thompson 's algorithm )을 사용하면 정규식을 동일한 언어를 수용하는 (최소) NFA로 쉽게 변환 할 수 있습니다 . 그러나 다른 방향은 더 지루한 것처럼 보이고 때로는 결과 표현이 지저분합니다.
NFA를 동등한 정규식으로 변환하기위한 알고리즘은 무엇입니까? 시간 복잡성 또는 결과 크기와 관련하여 장점이 있습니까?
이것은 참조 질문이어야합니다. 방법에 대한 일반적인 설명과 사소한 예를 포함하십시오.
표준 알고리즘 (예 : Thompson 's algorithm )을 사용하면 정규식을 동일한 언어를 수용하는 (최소) NFA로 쉽게 변환 할 수 있습니다 . 그러나 다른 방향은 더 지루한 것처럼 보이고 때로는 결과 표현이 지저분합니다.
NFA를 동등한 정규식으로 변환하기위한 알고리즘은 무엇입니까? 시간 복잡성 또는 결과 크기와 관련하여 장점이 있습니까?
이것은 참조 질문이어야합니다. 방법에 대한 일반적인 설명과 사소한 예를 포함하십시오.
답변:
유한 오토마타에서 정규 표현식으로 변환하는 방법에는 여러 가지가 있습니다. 여기서는 학교에서 일반적으로 가르치는 것을 매우 시각적으로 설명합니다. 실제로 가장 많이 사용된다고 생각합니다. 그러나 알고리즘을 작성하는 것은 좋은 생각이 아닙니다.
이 알고리즘은 오토 마톤 그래프 처리에 관한 것이므로 ... 상태 제거와 같은 그래프 프리미티브가 필요하기 때문에 알고리즘에 적합하지 않습니다. 고급 프리미티브를 사용하여 설명하겠습니다.
가장자리 레이블을 일관되게 유지하면서 가장자리에 정규 표현식을 사용한 다음 중간 상태를 제거하는 것이 좋습니다.
주요 패턴은 다음 그림에서 볼 수 있습니다. 첫 번째는 사이 라벨이 정규식 E , F , G , H , I를 우리는 제거 할 질문을 .
일단 제거되면 함께 구성합니다 ( p 와 r 사이의 다른 가장자리는 유지 하지만 이것에는 표시되지 않음).
Raphael의 답변 과 동일한 예를 사용하십시오 .
우리는 연속적으로 제거합니다 .
그리고 :
우리는 여전히에서 발현에 별을 적용해야 로 질문 1 . 이 경우 최종 상태도 초기이므로 별표 만 추가하면됩니다.
L[i,j]
에서 q j 까지의 언어 정규식입니다 . 먼저 모든 다중 에지를 제거합니다.
for i = 1 to n:
for j = 1 to n:
if i == j then:
L[i,j] := ε
else:
L[i,j] := ∅
for a in Σ:
if trans(i, a, j):
L[i,j] := L[i,j] + a
이제 상태 제거. 우리는 상태 제거 할 가정 :
remove(k):
for i = 1 to n:
for j = 1 to n:
L[i,i] += L[i,k] . star(L[k,k]) . L[k,i]
L[j,j] += L[j,k] . star(L[k,k]) . L[k,j]
L[i,j] += L[i,k] . star(L[k,k]) . L[k,j]
L[j,i] += L[j,k] . star(L[k,k]) . L[k,i]
star(ε)=ε
e.ε=e
∅+e=e
∅.e=∅
q k q j q k
이제 어떻게 사용 remove(k)
합니까? 최종 또는 초기 상태를 가볍게 제거하면 안됩니다. 그렇지 않으면 언어의 일부를 놓치게됩니다.
for i = 1 to n:
if not(final(i)) and not(initial(i)):
remove(i)
최종 상태 와 초기 상태 있는 경우 최종 표현식은 다음과 같습니다.q s
e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])
여러 최종 상태 (또는 초기 상태)가있는 경우 전이 폐쇄 방법을 적용하는 것 외에 이러한 상태를 병합하는 간단한 방법은 없습니다. 일반적으로 이것은 손으로 문제가되지 않지만 알고리즘을 작성할 때 어색합니다. 훨씬 간단한 해결 방법은 모든 쌍을 열거하는 것입니다 과 모든 식을 얻을 수있는 (이미 상태가-제거) 그래프의 알고리즘 실행 랬 유일한 초기 상태이며, 최종 만입니다 state 다음 모든 결합을 수행합니다 .e s , f s f e s , f
이것은 첫 번째 방법보다 언어를 동적으로 수정한다는 사실 때문에 프로그래밍시 오류가 발생하기 쉽습니다. 다른 방법을 사용하는 것이 좋습니다.
이 알고리즘에는 제거해야 할 노드, 마지막 최종 상태 수, 최종 상태가 초기 상태 일 수 있다는 사실 등을 선택하는 경우가 많이 있습니다.
알고리즘이 작성되었으므로 이는 전이 폐쇄 방법과 매우 유사합니다. 사용법의 상황 만 다릅니다. 알고리즘을 구현하지 않는 것이 좋지만 방법을 사용하여 직접 수행하는 것이 좋습니다.
ab
내가 본 가장 좋은 방법은 해결할 수있는 (일반) 언어의 방정식 시스템으로 오토 마톤을 표현하는 방법입니다. 다른 방법보다 간결한 표현을 얻는 것처럼 보이기 때문에 특히 좋습니다.
하자 없이 NFA를 -transitions. 모든 상태 에 대해 방정식을 만듭니다ε q i
여기서 는 최종 상태 세트이며 는 에서 로 레이블이 지정된 전이가 있음을 의미 . 을 또는 (정규 표현식 정의에 따라) 로 읽으면 이것이 정규 표현식의 방정식임을 알 수 있습니다.q i a → q j q i
시스템을 해결하려면 및 (문자열 연결) 의 연관성 및 분 , 정류 성 및 Arden의 Lemma ¹가 필요합니다.
하자 정기적 인 언어 . 그때,
솔루션은 모든 상태 하나씩 정규 표현식 세트입니다 . 는 에서 시작할 때 가 받아 들일 수있는 단어를 정확하게 설명합니다 . 따라서 (만약 초기 상태 인) 원하는 표현이다.
명확성을 위해 싱글 톤 세트를 해당 요소로 표시합니다 (예 : . 예는 Georg Zetzsche 때문입니다.
이 NFA를 고려하십시오.
[ 출처 ]
해당 방정식 시스템은 다음과 같습니다.
이제 세 번째 방정식을 두 번째 방정식에 연결하십시오.
마지막 단계에서는 , 및 인 Arden의 Lemma를 적용 합니다. 세 언어는 모두 정규 언어이고 이므로 우리는이 보조 을 적용 할 수 있습니다. 이제이 결과를 첫 번째 방정식에 연결합니다.
따라서, 우리는 상기 오토 마톤에 의해 수용되는 언어에 대한 정규 표현, 즉
그것은 간결하지만 (다른 방법의 결과와 비교) 독특하게 결정되지는 않았습니다. 방정식 시스템을 다른 조작 순서로 해결하면 다른 것과 동등한 결과가 도출됩니다! -표현.
maybe_union/2
술어는 더 많은 작업을 사용하여 (일반적인 접두사를 제거하는 등의) 더 많은 정규 표현식을 작성할 수 있습니다. 이 방법을 보는 또 다른 방법은 정규식에서 오른쪽 선형 문법으로의 변환으로 이해하는 것입니다. 여기서 Prolog와 같은 통일 또는 ML과 같은 패턴 일치를 사용하는 언어는 매우 좋은 변환기를 만들 수 있으므로 펜과 종이 만이 아닙니다. algorithm :)
이것은 Raphael의 답변에 설명 된 것과 동일한 방법 이지만 체계적인 알고리즘의 관점에서 실제로 알고리즘입니다. 어디에서 시작해야하는지 알고 나면 쉽고 자연스럽게 구현됩니다. 또한 모든 오토마타를 그리는 것이 어떤 이유로 비실용적이라면 수작업으로 더 쉬울 수도 있습니다.
알고리즘을 작성할 때 방정식을 항상 추상적으로 나타내야 방정식을 잘 표현할 수 있으므로 손으로 풀 때 잊을 수없는 것을 기억해야합니다.
나는 그것이 전에 읽도록 제안하는 Raphael의 답변 에서 잘 이루어 졌기 때문에 그것이 어떻게 작동하는지 설명하지 않을 것 입니다. 대신, 너무 많은 계산이나 추가 사례를 수행하지 않고 방정식을 풀어야하는 순서에 중점을 둡니다.
에서 시작 아덴의 규칙 의 독창적 인 솔루션 언어 방정식은 우리는 형태의 방정식의 집합으로 자동 기계를 고려할 수 있습니다 :
따라서 배열 및 를 업데이트하여 을 유도하여이 문제를 해결할 수 있습니다 . 단계에서 , 우리는 :
그리고 Arden의 규칙은 우리에게 :
및 설정함으로써 및 우리 얻을 :
그런 다음 시스템에서 을 설정하여 모든 요구를 제거 할 수 있습니다 .
우리가 해결되면 때 , 우리는 이런 식을 얻었다 :
아니오 . 따라서 정규 표현식을 얻었습니다.
덕분에 알고리즘을 구축 할 수 있습니다. 위의 유도에서와 동일한 규칙을 갖기 위해 초기 상태는 이고 상태 수는 입니다. 먼저 를 채우는 초기화 :
for i = 1 to m:
if final(i):
B[i] := ε
else:
B[i] := ∅
그리고 :
for i = 1 to m:
for j = 1 to m:
for a in Σ:
if trans(i, a, j):
A[i,j] := a
else:
A[i,j] := ∅
그리고 해결 :
for n = m decreasing to 1:
B[n] := star(A[n,n]) . B[n]
for j = 1 to n:
A[n,j] := star(A[n,n]) . A[n,j];
for i = 1 to n:
B[i] += A[i,n] . B[n]
for j = 1 to n:
A[i,j] += A[i,n] . A[n,j]
최종 표현은 다음과 같습니다.
e := B[1]
알고리즘에 대해 너무 상징적으로 보이는 방정식 시스템처럼 보일 수도 있지만, 이는 구현에 적합합니다. 다음은 Ocaml에서이 알고리즘을 구현 한 것입니다 (깨진 링크) . 함수 외에 brzozowski
, 모든 것은 Raphael의 예제를 위해 인쇄하거나 사용하는 것입니다. 정규 표현식 단순화를위한 놀랍도록 효율적인 기능이 simple_re
있습니다.
이 방법은 알고리즘의 형태로 작성하기 쉽지만 터무니없이 큰 정규 표현식을 생성하며 직접 작성하는 경우 비실용적입니다. 대부분 너무 체계적이기 때문입니다. 그래도 알고리즘에는 좋고 간단한 솔루션입니다.
보자 문자열에 대한 정규 표현식에서가는 대표 하는 상태 사용하여 . 오토 마톤의 상태 수로 하자 .
모든 대해 중간 상태 (사지 제외) 없이 에서 정규식 을 이미 알고 있다고 가정하십시오 . 그런 다음 다른 상태를 추가하면 새로운 정규 표현식 어떤 영향 을 알 수 있습니다. 직접 전환하는 경우에만 변경되며 다음과 같이 표현 될 수 있습니다.
( 은 이고 는 입니다.)
우리는 Raphael의 답변 에서와 동일한 예제를 사용할 것 입니다. 처음에는 직접 전환 만 사용할 수 있습니다.
첫 번째 단계는 다음과 같습니다 (라벨 가 있는 자체 루프 는 첫 번째 을 로 변환했을 것입니다 .
두 번째 단계에서는 을 사용할 수 있습니다 ( 이 이미 위의 목적으로 사용 되었기 때문에 로 이름이 바 us ). 작동 방식을 살펴 보겠습니다 .
에서 하는 : .
왜 그런 겁니까? 그것은에서 가고 있기 때문이다 위해 에만 사용 중간 상태가 여기있을 (수행 할 수 ) 또는 것 ( 이 루핑) ( )하고 돌아 오는 ( ).
당신은 같이 계산할 수있는 및 도 및 때문에 당신에게 최종 식을 줄 것이다 초기 및 최종 양이다. 많은 표현 단순화가 여기서 이루어졌습니다. 그렇지 않으면 제 의 이 될 것이다 상기 제 의 것 .
초기화 :
for i = 1 to n:
for j = 1 to n:
if i == j:
R[i,j,0] := ε
else:
R[i,j,0] := ∅
for a in Σ:
if trans(i, a, j):
R[i,j,0] := R[i,j,0] + a
전이 폐쇄 :
for k = 1 to n:
for i = 1 to n:
for j = 1 to n:
R[i,j,k] := R[i,j,k-1] + R[i,k,k-1] . star(R[k,k,k-1]) . R(k,j,k-1)
그런 다음 최종 표현식은 다음과 ( 를 초기 상태로 가정 ).
e := ∅
for i = 1 to n:
if final(i):
e := e + R[s,i,n]
그러나 그것이 추악한 정규 표현식을 생성한다고 상상할 수 있습니다. 실제로 와 동일한 언어를 나타내는 와 같은 것을 기대할 수 있습니다 . 정규 표현식을 단순화하는 것이 실제로 유용합니다.a a