Introduction
Automata theory is a foundational area of theoretical computer science and discrete mathematics
that studies abstract machines and computational problems. It provides a rigorous framework for understanding
formal languages, state-based computation, and algorithmic processes. For instance, automata theory
is applied in compiler design, natural language processing, software verification, and the study of complexity
classes in the theory of computation. By working through its basic models, we will develop a deeper
understanding of computation, logic, and the mathematical principles behind algorithms.
On this page, we introduce the simplest computational model, the finite automaton. The languages
it recognizes form the class of regular languages. We then study the regular operations (union,
concatenation, and Kleene star), the equivalence between deterministic and nondeterministic machines, and the
connection to regular expressions.
Finite Automata & Regular Languages
Real computers are enormously complex, so to study the fundamental principles of computation, we introduce
simplified computational models that isolate specific features. The simplest such model
is the finite automaton (also called a finite state machine).
Definition: Finite Automaton (DFA)
A finite automaton is a 5-tuple \((Q, \Sigma, \delta, q_0, F)\) where
- \(Q\) is a finite set called the states,
- \(\Sigma\) is a finite set called the alphabet,
- \(\delta: Q \times \Sigma \to Q\) is the transition function,
- \(q_0 \in Q\) is the start state,
- \(F \subseteq Q\) is the set of accept states.
Informally, a finite automaton starts in its start state \(q_0\), reads an input string over \(\Sigma\) one symbol at a
time, and moves between states according to \(\delta\). It accepts the string if the state it reaches after the final
symbol lies in \(F\), and rejects it otherwise. The next definition makes this precise.
Definition: Computation and Acceptance
Let \(M = (Q, \Sigma, \delta, q_0, F)\) be a finite automaton and let \(w = w_1 w_2 \cdots w_n\) be a
string over \(\Sigma\) (where each \(w_i \in \Sigma\), and the case \(n = 0\) corresponds to the empty
string \(\epsilon\)). A computation of \(M\) on \(w\) is a sequence of states
\(r_0, r_1, \ldots, r_n \in Q\) satisfying:
- \(r_0 = q_0\),
- \(r_{i+1} = \delta(r_i, w_{i+1})\) for each \(i = 0, 1, \ldots, n-1\).
We say that \(M\) accepts \(w\) if \(r_n \in F\), and rejects \(w\)
otherwise. The language recognized by \(M\) is then defined formally as
\[
L(M) = \{\, w \in \Sigma^* \mid M \text{ accepts } w \,\}.
\]
Here \(\Sigma^*\) denotes the set of all strings over \(\Sigma\), including \(\epsilon\).
We also say that \(M\) recognizes the language \(L(M)\). A language is called a
regular language if some finite automaton recognizes it.
Consider the following finite automaton.
This directed graph, whose arrows are labeled by input symbols, is called the state diagram of the
finite automaton. The automaton has three states, \(Q = \{q_0, q_1, q_2\}\). The start state is \(q_0\), and the only
accept state is \(q_1\), drawn as a double circle, so \(F = \{q_1\}\). The arrows, including the self-loops, represent
the transitions given by \(\delta\). An arrow from \(q\) to \(q'\) labeled \(a\) means \(\delta(q, a) = q'\), and a label
such as \(0, 1\) combines the transitions for several symbols into a single arrow.
This automaton processes an input string over the alphabet (in this case, \(\Sigma = \{0, 1\}\)) and returns either
"accept" or "reject" as output.
For example, if the input string is 1101101, this automaton processes the string as follows:
Start reading
Read 1, move from \(q_0\) to \(q_1\)
Read 1, stay in \(q_1\)
Read 0, move to \(q_2\)
Read 1, move to \(q_1\)
Read 1, stay in \(q_1\)
Read 0, move to \(q_2\)
Read 1, move to \(q_1\)
End reading
Return "accept."
This automaton recognizes strings that end with a 1 followed by an even number of 0s. For example, 10100 is
accepted because it ends with 1 followed by two 0s, but 1011000 is rejected because it ends with three 0s after
the last 1. Note that a string ending in 1 (with zero trailing 0s) is also accepted, since zero is even.
Regular Operations
We now define three fundamental operations on languages. A key result is that regular languages are
closed under all three. Applying any of these operations to regular languages always yields
another regular language.
Definition: Regular Operations
Let \(A\) and \(B\) be languages over an alphabet \(\Sigma\).
- Union:
\(A \cup B = \{x \mid x \in A \text{ or } x \in B\}\)
- Concatenation:
\(A \circ B = \{xy \mid x \in A \text{ and } y \in B\}\)
Note: \(\emptyset \circ A = \emptyset\), and \(\{\epsilon\} \circ A = A\).
We define the powers \(A^0 = \{\epsilon\}\) and \(A^{k+1} = A^k \circ A\) for \(k \geq 0\), so that \(A^1 = A\)
and \(A^2 = A \circ A\).
- Kleene Star:
\(A^* = \{x_1 x_2 \cdots x_k \mid k \geq 0 \text{ and each } x_i \in A\}\)
Equivalently, \(A^* = A^0 \cup A^1 \cup A^2 \cup \cdots\).
Note: The empty string \(\epsilon\) is always in \(A^*\) (the case \(k = 0\)).
In particular, \(\emptyset^* = \{\epsilon\}\)
since \(\emptyset^* = \emptyset^0 \cup \emptyset^1 \cup \emptyset^2 \cup \cdots\).
For example, let \(\Sigma = \{a, \, b, \, c, \, \ldots, \, x, \, y, \, z\}\).
If \(A = \{black, \, white\}\) and \(B = \{dog, \, cat\}\), then
\[
\begin{align*}
&A \cup B = \{black, \, white, \, dog, \, cat \} \\\\
&A \circ B = \{blackdog, \, blackcat, \, whitedog, \, whitecat\} \\\\
&A^* = \{\epsilon, \, black, \, white, \, blackblack, \, blackwhite, \, whiteblack, \, whitewhite, \, blackblackblack, \, blackblackwhite, \, \ldots\}.
\end{align*}
\]
Theorem 1: Closure of Regular Languages
The class of regular languages is closed under the regular operations. That is,
if \(A\) and \(B\) are regular languages, then \(A \cup B\), \(A \circ B\),
\(A^*\), and \(B^*\) are also regular.
Proof: Closure under union
Suppose that two finite automata
\[
M_1 = (Q_1, \Sigma, \delta_1, q_{0_1}, F_1)
\]
and
\[
M_2 = (Q_2, \Sigma, \delta_2, q_{0_2}, F_2)
\]
recognize the regular languages \(A_1\) and \(A_2\), respectively.
We construct \(M_3\) to recognize \(A_1 \cup A_2\) as follows:
\[
M_3 = (Q_3, \Sigma, \delta_3, q_{0_3}, F_3)
\]
where
\[
\begin{align*}
Q_3
&= Q_1 \times Q_2 \\\\
&= \{(q_1, q_2) \mid q_1 \in Q_1 \text{ and } q_2 \in Q_2\},
\end{align*}
\]
\[
\delta_3((q_1, q_2), a) = (\delta_1(q_1, a), \delta_2(q_2, a)) \quad \forall a \in \Sigma,
\]
\[
q_{0_3} = (q_{0_1}, q_{0_2}),
\]
\[
F_3 = \{(q_1, q_2) \mid q_1 \in F_1 \text{ or } q_2 \in F_2 \},
\]
which is the same as \((F_1 \times Q_2) \cup (Q_1 \times F_2)\).
Verification.
The key claim is the following: for any input string \(w\), after \(M_3\)
reads \(w\), its state is exactly \((r_1, r_2)\), where \(r_i\) is the state \(M_i\) reaches after reading
\(w\). The claim follows by a straightforward induction on \(|w|\). The crucial point is that \(\delta_3\) acts
component-wise, updating the first component by \(\delta_1\) and the second by \(\delta_2\), independently
of each other. Once this claim is established, the acceptance condition unfolds as
\[
\begin{align*}
M_3 \text{ accepts } w
&\iff (r_1, r_2) \in F_3 \\\\
&\iff r_1 \in F_1 \text{ or } r_2 \in F_2 \\\\
&\iff M_1 \text{ accepts } w \text{ or } M_2 \text{ accepts } w.
\end{align*}
\]
Therefore \(L(M_3) = A_1 \cup A_2\), proving that \(A_1 \cup A_2\) is regular.
Note: The same product construction with \(F_3 = F_1 \times F_2\) (instead of the union of slices) recognizes
\(A_1 \cap A_2\), since now \(M_3\) accepts only when both \(M_1\) and \(M_2\) accept. This shows
that the class of regular languages is also closed under intersection.
Closure under concatenation and Kleene star.
The product construction we just used for union
does not adapt cleanly to concatenation or Kleene star. The reason is that a DFA reading \(w\) must commit, at every
position, to a single next state. To recognize \(A_1 \circ A_2\), however, a machine that follows a single run of \(M_1\)
and then of \(M_2\) would need to decide where the prefix from \(A_1\) ends and the suffix from \(A_2\) begins, and this
split point is not visible from the input alone. Kleene star raises the same difficulty, since a machine for \(A^*\)
that follows successive runs of a single DFA for \(A\) would have to determine how many times to restart that DFA
and where each repetition begins.
The natural remedy is to let the machine "guess", which is exactly what a nondeterministic finite
automaton (NFA) does. We introduce NFAs in the next section. Once NFAs are available, the constructions become
straightforward. For concatenation, we add an \(\epsilon\)-transition from each accept state of an NFA for \(A_1\) to
the start state of an NFA for \(A_2\). For Kleene star, we add \(\epsilon\)-transitions from each
old accept state back to the old start state, together with a new accepting start state that has an
\(\epsilon\)-transition to the old one.
The explicit constructions appear in the proof of
Theorem 3, as the concatenation and star
cases of the direction from regular expressions to automata. Suppose that \(A_1\) and \(A_2\) are regular, so that
some DFAs recognize them. Since every DFA can be viewed as an NFA, these constructions yield NFAs recognizing
\(A_1 \circ A_2\) and \(A_1^*\). By the
NFA-DFA equivalence, these NFAs have
equivalent DFAs. Hence \(A_1 \circ A_2\) and \(A_1^*\) are regular, completing the closure result.
Deterministic & Nondeterministic Machine
So far, we have considered deterministic computation, in which the current state and input symbol
uniquely determine the next state at each step. However, what if a computation is allowed multiple choices for the
next state? Such a computation is called nondeterministic. Every deterministic finite automaton
(DFA) is trivially a nondeterministic finite automaton (NFA), since determinism
is a special case of nondeterminism.
Definition: Nondeterministic Finite Automaton (NFA)
A nondeterministic finite automaton is a 5-tuple \((Q, \Sigma, \delta, q_0, F)\) where
- \(Q\) is a finite set of states,
- \(\Sigma\) is a finite alphabet,
- \(\delta: Q \times \Sigma_{\epsilon} \to \mathcal{P}(Q)\) is the transition function,
- \(q_0 \in Q\) is the start state,
- \(F \subseteq Q\) is the set of accept states,
where \(\Sigma_{\epsilon} = \Sigma \cup \{\epsilon\}\) and \(\mathcal{P}(Q)\) is the power set of \(Q\).
The transition \(\delta(q, \epsilon)\) defines an \(\epsilon\)-transition. Such a transition lets
the NFA move from state \(q\) to any state in \(\delta(q, \epsilon)\) without consuming any input symbol.
This is one of the key features that distinguishes NFAs from DFAs and will play a central role when we build
NFAs from regular expressions.
Note: The power set of a set is the set of all possible subsets of that set, including the empty set and the set itself. For example,
if \(S = \{a, b, c\}\), then \(\mathcal{P}(S) = \{\emptyset, \{a\}, \{b\}, \{c\}, \{a, b\}, \{a, c\}, \{b, c\}, \{a, b, c\}\} \). In general,
a set with \(n\) elements has a power set with \(2^n\) elements.
With this definition, a DFA with transition function \(\delta_D: Q \times \Sigma \to Q\) becomes an NFA once we set
\(\delta_N(q, a) = \{\delta_D(q, a)\}\) for each \(a \in \Sigma\) and \(\delta_N(q, \epsilon) = \emptyset\).
At each state, the NFA can split itself into multiple branches and follow all of them in parallel.
In other words, the NFA can have multiple active states at once, while the DFA always has a single active state.
If at least one of these branches reaches an accept state, then the whole computation accepts.
Definition: Acceptance by an NFA
Let \(N = (Q, \Sigma, \delta, q_0, F)\) be an NFA and let \(w\) be a string over \(\Sigma\). We say that \(N\)
accepts \(w\) if \(w\) can be written as \(w = y_1 y_2 \cdots y_m\) with each \(y_i \in \Sigma_{\epsilon}\)
and there is a sequence of states \(r_0, r_1, \ldots, r_m \in Q\) satisfying:
- \(r_0 = q_0\),
- \(r_{i+1} \in \delta(r_i, y_{i+1})\) for each \(i = 0, 1, \ldots, m-1\),
- \(r_m \in F\).
Since some \(y_i\) may equal \(\epsilon\), the length \(m\) can exceed the length of \(w\). The
language recognized by \(N\) is
\[
L(N) = \{\, w \in \Sigma^* \mid N \text{ accepts } w \,\}.
\]
Note: The NFA sounds like parallel computation, but in "actual" parallel computation, multiple processors or threads execute
tasks concurrently, often with considerations for synchronization, communication, and resource sharing. NFAs do not model
these practical concerns.
Theorem 2: NFA-DFA Equivalence
Every NFA has an equivalent DFA. That is, for every NFA \(N\), there exists a DFA \(D\)
such that \(L(N) = L(D)\).
Proof Sketch: Subset Construction
Let \(N = (Q, \Sigma, \delta, q_0, F)\) be an NFA. The key idea is that since \(N\) can be in several
states simultaneously, a single state of the equivalent DFA should encode which subset of \(Q\)
the NFA is currently in. We first treat the case without \(\epsilon\)-transitions. The general case
is handled by a small modification described at the end.
Construct \(D = (Q', \Sigma, \delta', q_0', F')\) as follows:
- \(Q' = \mathcal{P}(Q)\). Each DFA state is a subset of NFA states.
- \(\delta'(R, a) = \displaystyle\bigcup_{r \in R} \delta(r, a)\). From a subset \(R\), the new subset is the union
of all states reachable from any \(r \in R\) on input \(a\).
- \(q_0' = \{q_0\}\). The DFA starts in the singleton subset containing the NFA's start state.
- \(F' = \{R \subseteq Q \mid R \cap F \neq \emptyset\}\). A subset is accepting if it contains at least one
NFA accept state.
Correctness. By induction on the length of the input, one shows that after reading
\(w = w_1 w_2 \cdots w_n\), the DFA \(D\) is in the state \(R_n \subseteq Q\) consisting of exactly
those NFA states that \(N\) can reach after reading \(w\). Therefore \(D\) accepts \(w\) if and only if
some such reachable state lies in \(F\), which is precisely the condition for \(N\) to accept \(w\).
Hence \(L(D) = L(N)\).
Handling \(\epsilon\)-transitions. For each \(R \subseteq Q\), define the
\(\epsilon\)-closure \(E(R)\) as the set of all states reachable from \(R\) by following
zero or more \(\epsilon\)-transitions. Modify the construction by setting
\(\delta'(R, a) = E\bigl(\bigcup_{r \in R} \delta(r, a)\bigr)\) and
\(q_0' = E(\{q_0\})\). The same inductive argument then carries through.
Importantly, this theorem says that NFAs and DFAs recognize the same class of languages, namely the
regular languages. However, an NFA is often much smaller and easier to design than its equivalent DFA. The conversion
(known as the power set construction or subset construction) can produce a DFA with
up to \(2^{|Q|}\) states, and this blow-up cannot be avoided in general. For instance, the strings over \(\{0, 1\}\)
whose \(n\)-th symbol from the end is \(1\) are recognized by an NFA with \(n + 1\) states, while every DFA for this
language needs at least \(2^n\) states, a fact we state without proof.
We now introduce a third way to describe regular languages, using algebraic expressions rather than machines.
Regular Expressions
Definition: Regular Expression
\(R\) is a regular expression over the alphabet \(\Sigma\) if \(R\) is one of the following:
- \(a\) for some \(a \in \Sigma\) (the language \(\{a\}\)),
- \(\epsilon\) (the language \(\{\epsilon\}\) containing only the empty string),
- \(\emptyset\) (the empty language, containing no strings),
- \((R_1 \cup R_2)\), where \(R_1\) and \(R_2\) are regular expressions,
- \((R_1 \circ R_2)\), where \(R_1\) and \(R_2\) are regular expressions,
- \((R_1^*)\), where \(R_1\) is a regular expression.
This is an inductive definition, in which cases 4-6 build larger expressions from smaller ones.
Each regular expression \(R\) describes a language \(L(R)\). Cases 1-3 describe the languages indicated in
parentheses, and cases 4-6 describe \(L(R_1) \cup L(R_2)\), \(L(R_1) \circ L(R_2)\), and \(L(R_1)^*\), respectively.
In practice we abbreviate \((R_1 \circ R_2)\) as \(R_1 R_2\) and omit parentheses when no ambiguity arises. Star
binds tighter than concatenation, and concatenation binds tighter than union.
Theorem 3: Equivalence of Regular Expressions and Finite Automata
A language is regular if and only if some regular expression describes it. That is:
- If a language is described by a regular expression, then it is regular.
- If a language is regular, then it is described by a regular expression.
Proof of Statement 1 (Regex → NFA)
The proof is by structural induction on the regular expression \(R\). The base cases
(1, 2, 3) handle the atomic regexes built without combination. The inductive cases (4, 5, 6) build an
NFA for \(R\) from NFAs for the smaller subexpressions \(R_1, R_2\). For each case, we exhibit an NFA
\(M\) recognizing \(L(R)\), which establishes regularity (since any language recognized by an NFA is
regular by Theorem 2).
- \(R = a\) for some \(a \in \Sigma\):
Then
\[
L(R) = \{a\}
\]
and the following NFA recognizes \(L(R)\):
\[
M = (\{q_0 , q_1\}, \Sigma, \delta, q_0, \{q_1\})
\]
where \(\delta(q_0, a) = \{q_1\}\) and \(\delta(r, b) = \emptyset\) for \(r \neq q_0\) or \(b \neq a\).
- \(R =\epsilon\):
Then
\[
L(R) = \{\epsilon\}
\]
and the following NFA recognizes \(L(R)\):
\[
M = (\{q_0\}, \Sigma, \delta, q_0, \{q_0\})
\]
where \(\delta(r, b) = \emptyset\) for any \(r\) and \(b\).
- \(R = \emptyset\):
Then
\[
L(R) = \emptyset
\]
and the following NFA recognizes \(L(R)\):
\[
M = (\{q\}, \Sigma, \delta, q, \emptyset)
\]
where \(\delta(r, b) = \emptyset\) for any \(r\) and \(b\).
For cases 4, 5, and 6, let \(M_1 = (Q_1, \Sigma, \delta_1, q_{0_1}, F_1)\) be an NFA that recognizes \(L(R_1)\) and
\(M_2 = (Q_2, \Sigma, \delta_2, q_{0_2}, F_2)\) be an NFA that recognizes \(L(R_2)\). We assume that the state sets
\(Q_1\) and \(Q_2\) are disjoint.
- \(R = (R_1 \cup R_2)\):
Then
\[
L(R) = L(R_1) \cup L(R_2)
\]
and the following NFA recognizes \(L(R)\):
\[
M = ( Q_1 \cup Q_2 \cup \{q_0\}, \Sigma, \delta, q_0, F_1 \cup F_2)
\]
where \(q_0\) is a new state not in \(Q_1 \cup Q_2\) and, for any \(q \in Q_1 \cup Q_2 \cup \{q_0\}\) and
any \(a \in \Sigma_{\epsilon}\),
\[
\delta(q, a) = \begin{cases}
\delta_1(q, a) &\text{if \(q \in Q_1\)}, \\
\delta_2(q, a) &\text{if \(q \in Q_2\)}, \\
\{q_{0_1}, q_{0_2}\} &\text{if \(q = q_0\) and \(a = \epsilon\)}, \\
\emptyset &\text{if \(q = q_0\) and \(a \neq \epsilon\)}.
\end{cases}
\]
Verification.
On input \(w\), \(M\) starts at \(q_0\) and immediately \(\epsilon\)-branches
into both \(q_{0_1}\) and \(q_{0_2}\), after which the two branches process \(w\) as \(M_1\) and
\(M_2\) independently. Since the accept set is \(F_1 \cup F_2\), the machine \(M\) accepts \(w\) if and only
if at least one of \(M_1, M_2\) accepts \(w\), that is, if and only if \(w \in L(R_1) \cup L(R_2)\).
- \(R = (R_1 \circ R_2)\):
Then
\[
L(R) = L(R_1) \circ L(R_2)
\]
and the following NFA recognizes \(L(R)\):
\[
M = ( Q_1 \cup Q_2 , \Sigma, \delta, q_{0_1}, F_2)
\]
where for any \(q \in Q_1 \cup Q_2\) and any \(a \in \Sigma_{\epsilon}\),
\[
\delta(q, a) = \begin{cases}
\delta_1(q, a) &\text{if \(q \in Q_1\) and \(q \not\in F_1\) }, \\
\delta_1(q, a) &\text{if \(q \in F_1\) and \(a \neq \epsilon\) }, \\
\delta_1(q, a) \cup \{q_{0_2}\} &\text{if \(q \in F_1\) and \(a = \epsilon\) }, \\
\delta_2(q, a) &\text{if \(q \in Q_2\) }.
\end{cases}
\]
Verification.
On input \(w\), \(M\) starts at \(q_{0_1}\) and processes \(w\) as \(M_1\).
Whenever the computation enters an \(F_1\) state, an \(\epsilon\)-transition to \(q_{0_2}\) becomes
available, and the NFA may nondeterministically either take it or continue inside \(M_1\).
Once it jumps, the rest of \(w\) is processed as \(M_2\). Since the accept set is \(F_2\) only,
\(M\) accepts \(w\) if and only if \(w\) splits as \(w = uv\) with \(u \in L(R_1)\) and \(v \in L(R_2)\),
that is, if and only if \(w \in L(R_1) \circ L(R_2)\).
- \(R = (R_1^*) \):
Then
\[
L(R) = L(R_1)^*
\]
and the following NFA recognizes \(L(R)\):
\[
M = ( Q_1 \cup \{q_0\}, \Sigma, \delta, q_0, \{q_0\} \cup F_1)
\]
where \(q_0\) is a new state not in \(Q_1\) and, for any \(q \in Q_1 \cup \{q_0\}\) and any
\(a \in \Sigma_{\epsilon}\),
\[
\delta(q, a) = \begin{cases}
\delta_1(q, a) &\text{if \(q \in Q_1\) and \(q \not\in F_1\) }, \\
\delta_1(q, a) &\text{if \(q \in F_1\) and \(a \neq \epsilon\) }, \\
\delta_1(q, a) \cup \{q_{0_1}\} &\text{if \(q \in F_1\) and \(a = \epsilon\) }, \\
\{q_{0_1}\} &\text{if \(q = q_0\) and \(a = \epsilon\) }, \\
\emptyset &\text{if \(q=q_0\) and \(a \neq \epsilon\)}.
\end{cases}
\]
Verification.
The new start state \(q_0\) is itself accepting, so on input \(\epsilon\)
the machine \(M\) immediately accepts (and \(\epsilon \in L(R_1)^*\) holds for any \(R_1\)).
On a non-empty input, \(M\) first \(\epsilon\)-transitions to \(q_{0_1}\) and runs \(M_1\).
Whenever \(M_1\) reaches an \(F_1\) state, an \(\epsilon\)-transition back to \(q_{0_1}\) allows
another iteration of \(R_1\), and this loop can be repeated any number of times. Hence \(M\) accepts
\(w\) if and only if \(w\) splits as \(w = w_1 w_2 \cdots w_k\) with \(k \geq 0\) and each
\(w_i \in L(R_1)\), that is, if and only if \(w \in L(R_1)^*\).
As an example, we convert the regular expression \((01 \cup 1)^*\) into an NFA through a sequence of stages.
The language \(L\bigl((01 \cup 1)^*\bigr)\) consists of strings such as
\[
\{\epsilon, \, 01, \, 1, \, 0101, \, 11, \, 1101, \, 01011101, \, 11111, \, 01101011110111, \, \ldots\}
\]
and the following NFA recognizes this language. When the NFA follows an \(\epsilon\)-transition, it moves to the next state
without reading the next symbol from the input string.
Proof Sketch of Statement 2 (DFA → Regex via state elimination)
For the converse direction, suppose \(A\) is regular, so a DFA \(M\) recognizes \(A\). We must produce
a regular expression \(R\) with \(L(R) = A\). The construction proceeds via an intermediate object called
a generalized NFA (GNFA), in which transitions are labeled by regular expressions rather
than single symbols. A GNFA reads input by consuming, on each transition, any string matching that
transition's regex label.
Step 1: Convert \(M\) into a GNFA.
Add a new start state \(s\) with an \(\epsilon\)-edge
to \(M\)'s start state, and a new unique accept state \(t\) with \(\epsilon\)-edges from each of \(M\)'s
accept states. Call every state other than \(s\) and \(t\) internal. Finally, for every ordered pair
\((q_i, q_j)\) with \(q_i \neq t\) and \(q_j \neq s\), merge the transitions from \(q_i\) to \(q_j\) into one labeled by
the union of their labels, and add a transition labeled \(\emptyset\) where there was none. The resulting GNFA
recognizes exactly \(A\).
Step 2: State elimination.
Repeatedly remove internal states one at a time. To remove
an internal state \(q_{\mathrm{rip}}\), update each remaining transition \(q_i \to q_j\) (for
\(q_i \neq t\) and \(q_j \neq s\)) by replacing its old label \(R_{ij}\) with
\[
R_{ij}^{\text{new}} = R_{ij} \cup R_{i,\mathrm{rip}} \, R_{\mathrm{rip},\mathrm{rip}}^* \, R_{\mathrm{rip},j}.
\]
In words, the first term bypasses \(q_{\mathrm{rip}}\) entirely, while the second passes from \(q_i\) into
\(q_{\mathrm{rip}}\), loops there, and then continues to \(q_j\). This new label captures every path that previously
went through \(q_{\mathrm{rip}}\). The Kleene star is essential here, since it accounts for traversing the self-loop
at \(q_{\mathrm{rip}}\) any number of times.
Step 3: Read off the regex.
After all internal states are eliminated, only \(s\) and
\(t\) remain, connected by a single transition labeled with some regex \(R\). One can verify that each
elimination step preserves the recognized language, so the final \(R\) satisfies \(L(R) = A\).