Download Finite Automata: Implementing NFAs and Converting Them to DFAs and more Slides Theory of Automata in PDF only on Docsity!
Chapter Two:
Finite Automata
The problem with implementing NFAs is that, being nondeterministic,
they do not really define computational procedures for testing
language membership. To implement an NFA we must give a
computational procedure that can look at a string and decide whether
the NFA has at least one sequence of legal transitions on that string
leading to an accepting state. This seems to require searching through
all legal sequences for the given input string—but how?
One approach is to implement a direct backtracking search. Another is
to convert the NFA into a DFA and implement that instead. This
conversion is both useful and theoretically interesting: the fact that it
is always possible shows that in spite of their extra flexibility, NFAs
have exactly the same power as DFAs. They can define exactly the
regular languages.
An NFA Example
- L ( N ) is the language strings over the alphabet {0,1} that have a 1 as the next-to-last symbol
- We will implement it with backtracking search in Java
- We will use a three-dimensional transition array
- delta[s,c-'0'] will be an array of 0 or more possible next states
q 0 1 q 1 0,1 q 2
0,
- A nondeterministic finite-state automaton that
- recognizes strings of 0s and 1s with 1 as the
- next-to-last character. */ public class NFA1 {
/*
- The transition function represented as an array.
- The entry at delta[s,c-'0'] is an array of 0 or
- more ints, one for each possible move from
- state s on character c. */ private static int[][][] delta = {{{0},{0,1}}, // delta[q0,0], delta[q0,1] {{2},{2}}, // delta[q1,0], delta[q1,1] {{},{}}}; // delta[q2,0], delta[q2,1]
char c = in.charAt(pos++); // get char and advance int[] nextStates; try { nextStates = delta[s][c-'0']; } catch (ArrayIndexOutOfBoundsException ex) { return false; // no transition, just reject }
// At this point, nextStates is an array of 0 or // more next states. Try each move recursively; // if it leads to an accepting state return true.
for (int i=0; i < nextStates.length; i++) { if (accepts(nextStates[i], in, pos)) return true; }
return false; // all moves fail, return false }
- Test whether the NFA accepts the string.
- @param in the String to test
- @return true iff the NFA accepts on some path */ public static boolean accepts(String in) { return accepts(0, in, 0); // start in q0 at char 0 } }
Not object-oriented: all static methods
All recursive search information is carried in the parameters
Usage example:
if (NFA1.accepts(s)) ...
Parallel Search
• The previous implementation was a
backtracking search
- Try one sequence of moves
- If that fails, back up a try another
- Keep going until you find an accepting sequence,
or run out of sequences to try
• You can also search all sequences at once
• Instead of keeping track of one current state,
keep track of the set of all possible states
Bit-Coded Sets
- We'll use machine words to represent sets
- One bit position for each state, with a 1 at that position if the state is in the set
q 31 q 3 q 2 q 1 q 0
0 É 0 1 0 1
The set { q 1 , q 2 }
q 31 q 3 q 2 q 1 q 0
0 É 0 1 1 0
The set { q 0 , q 2 }
- A nondeterministic finite-state automaton that
- recognizes strings with 0 as the next-to-last
- character. */ public class NFA2 {
/*
- The current set of states, encoded bitwise:
- state i is represented by the bit 1<<i. */ private int stateSet;
/**
- Reset the current state set to {the start state}. */ public void reset() { stateSet = 1<<0; // {q0} }
- The transition function represented as an array.
- The set of next states from a given state s and
- character c is at delta[s,c-'0']. */ static private int[][] delta = {{1<<0, 1<<0|1<<1}, // delta[q0,0] = {q0} // delta[q0,1] = {q0,q1} {1<<2, 1<<2}, // delta[q1,0] = {q2} // delta[q1,1] = {q2} {0, 0}}; // delta[q2,0] = {} // delta[q2,1] = {}
- Test whether the NFA accepted the string.
- @return true iff the final set includes
- an accepting state */ public boolean accepted() { return (stateSet&(1<<2))!=0; // true if q2 in set } }
Usage example:
NFA2 m = new NFA2(); m.reset(); m.process(s); if (m.accepted()) ...
Larger NFAs
• Generalizes easily for NFAs of up to 32 states
• Easy to push it up to 64 (using long instead
of int)
• Harder to implement above 64 states
• Could use an array of n /32 int variables to
represent n states
• That would make process slower and more
complicated
From NFA To DFA
• For any NFA, there is a DFA that recognizes
the same language
• Proof is by construction: a DFA that keeps
track of the set of states the NFA might be in
• This is called the subset construction
• First, an example starting from this NFA:
q 0 0,1^ q 2
0,
(^1) q 1
- Initially, the set of states the NFA could be in
is just { q 0 }
- So our DFA will keep track of that using a
start state labeled { q 0 }:
q 0 0,1^ q 2
0,
(^1) q 1
...
...
1
0
{ q 0 }