Abstract
Finite Automata are a simple, but nonetheless useful, mathematical model of computation. In this chapter, we look at this model and at the languages that they can accept.
These lecture notes are intended to be read in concert with Chapter 2 of the text (Martin). I will both offer my own commentary on the text as appropriate and will also provide JFLAP files and possibly other study aids to enhance the text’s own examples.
Finite automata (FA), also widely known as finite state automata (FSA), are a mathematical model of computation based on the ideas of
A system changing state due to inputs supplied to it.
Some of these states being acceptor or final states.
This is, as we will see, a computation model of somewhat limited power. Not all things that we regard as “computation” can be done with FAs. It is, however, powerful enough to have quite few practical applications, while being simple enough to be easily understood.
A traditional puzzle:
A man stands on the side of a small river. He has with him a head cabbage, a goose, and a dog. On the shore in front of him is a small rowboat. The boat is so small that he can take only himself and one of his accompanying items at a time.
But:
If he leaves the goose alone on either shore with the cabbage, the goose will eat the cabbvage.
If he leaves the dog alone on either shore with hte goose, the dog will kill the goose.
How can the man get across the river with all his items intact?
We can model this by labeling situations using the characters M C G D |
to denote the man, the cabbage, the goose, the dog, and the river, respectively.
For example, we start with everyone on one side of the river: CDGM|
. (We will choose to always write the characters on either side of the river in alphabetic order.) We want to end up with everything on the other side of the river: |CDGM
.
Starting from CDGM|
, we can consider four possibilities:
DG|CM
.CD|GM
.CG|DM
.CDG|M
.We can diagram that like this:
The circles (states) are labeled with the positions of hte man and his items. The connecting arrows (transitions) are labeled with what the man took with him on his trip (using ‘x’ to indicate that he rowed back alone).
Now looking at this, we can see case 1 ends badly. The dog kills the goose. In case 3, the goose eats the cabbage. In case 4, one or both of those two things happens (depending on how fast the goose is). None of these are desirable, so I will mark them as “final”.
That leaves us with one non-final state to explore: CD|GM
. From here, we have two possibilities:
Case 1 actually takes us back to our starting state. Case 2 gives us a new possibility, CDM|G
.
From GDM|G
we have three possibilities:
The last case takes us to a state we have already seen. The other two are new, and neither leads to immediate disaster.
From D|CGM
we get three possibilities:
One of these is a previously inspected state. One of these is a final state. One is new.
From C|DGM
we get three possibilities:
Again, one of these is a previously inspected state. One of these is a final state. One is new.
From DGM|C
we have three possibilities:
Only one of these yields a new possibility.
From CGM|D
we have three possibilities:
This does not introduce any new possibilities.
From G|CDM
, we have three possibilities:
Only one new possibility is found.
From that new state, GM|CD
, we have two possibilities:
This finally connects us to the |CDGM
state that we were looking for. This is also a “final” state, in the sense that we need look no further.
We could, for completeness, fill out the chart by recording what would happen if the man kept rowing back and forth. But, since we have him safely on the other with all of his possessions, let’s let him continue on with his journey.
We can read the series of steps necessary to solve the puzzle by reading off the labels of all the transitions that take us from CDGM|
to |CDGM
:
G x D G C x G
What we have just built is a Finite Automaton (FA), a collection of states in which we make transitions based upon input symbols.
**Definition ** A Finite Automaton
A finite automaton (FA) is a 5-tuple $(Q, \Sigma, q_0, A, \delta)$ where
- $Q$ is a finite set of states;
- $\Sigma$ is a finite input alphabet;
- $q_0 \in Q$ is the initial state;
- $A \subseteq Q$ is the set of accepting states; and
- $\delta : Q \times \Sigma \rightarrow Q$ is the transition function.
For any element
q
ofQ
and any symbol $\sigma \in \Sigma$, we interpret $\delta(q,\sigma)$ as the state to which the FA moves, if it is in stateq
and receives the input $\sigma$.
This is the key definition in this chapter and is worth a look to be sure you understand it.
For example, for the FA shown here, we would say that:
$Q = \{ q_0, q_1, q_2, 0, 1, 2 \}$
These are simply labels for states, so we can use any symbol that is convenient.
$\Sigma = \{ 0, 1 \}$
These are the characters that we can supply as input.
$q_0$ is, well, $q_0$ because we chose to use that matching label for the state.
$A = \{ q_1, 0 \}$
$\delta = \{ ((q0, 0), q1), ((q0, 1), 1),$ $((q1, 0), q2), ((q1, 1), q2),$ $((q2, 0), q2), ((q2, 1), q2),$ $((0, 0), 0), ((0, 1), 1),$ $((1, 0), 2), ((1, 1), 0),$ $((2, 0), 1), ((2, 1), 2) \}$
Functions are, underneath it all, sets of pairs. The first element in each pair is the input to the function and the second element is the result. Hence when you see $((q0, 0), q1)$, it means that, "if the input is $(q0, 0)$, the result is $q1$.
The input is itself a pair because $\delta$ was defined as a function of the form $Q \times \Sigma \rightarrow Q$, so the input has the form $Q \times \Sigma$, the set of all pairs in which the first element is taken from set $Q$ and the second element from set $\Sigma$.
Of course, there may be easier ways to visualize $\delta$. In particular, we could do it via a table with the input state on one axis and the input character on another:
Starting State | ||||||
---|---|---|---|---|---|---|
Input | q0 | q1 | q2 | 0 | 1 | 2 |
0 | q1 | q2 | q2 | 0 | 2 | 1 |
1 | 1 | q2 | q2 | 1 | 0 | 2 |
The table representation is particularly useful because it suggests an efficient implementation. If we numbered our states instead of using arbitrary labels:
Starting State | ||||||
---|---|---|---|---|---|---|
Input | 0 | 1 | 2 | 4 | 5 | 6 |
0 | 1 | 2 | 2 | 3 | 5 | 4 |
1 | 4 | 2 | 2 | 4 | 3 | 5 |
then we could implement this FA as follows:
char c;
int state = 0;
while (cin >> c)
{
int charNumber = c - '0';
state = delta[state][charNumber];
}
Look at this automaton.
Look at this automaton.
A little bit trickier.
This automaton accepts strings over ${0,1}$, wo we can think of it as accepting certain binary numbers.
This chapter has looked at a very “pure” form of FSA. There are some common variations that allow us to “do” things with an FA without substantially altering the computation power beyond that of a regular FA.
One worth mentioning is the Mealy machine, which adds to each state transition an optional string to be emitted.
Here, for example, is a Mealy machine that translates binary numbers into octal (base 8).
Some of the more complicated automata that we will examine in later chapters can be thought of as associating some form of I/O device with a Mealy machine “controller”. Thinking of an FA as a controller may make a little more sense if we believe that the FA can issue output other than simply accepting to not accepting a completed string, output that could be signals to some other device.
Although FAs are limited in computational power, they have the virtue of being relatively easy to understand. Consequently, they are often used by software developers and non-software system designers as a means of summarizing the behavior of a complex system.
Here, for example, is a state diagram summarizing the behavior of processes in a typical operating system. Most operating systems will be running more processes than can be simultaneously accommodated on the number of physical CPUs. Therefore newly started processes start in a Ready
state and have to wait until the OS scheduler assigns a CPU to them. At that moment, the process starts Running
. It stays in this state until either the scheduler decides to take back the CPU (because a “time slice” has expired) or because the process has initiated an I/O operation that, compared to the CPU speed, may take a substantial amount of time. In the latter case, the process moves into a Blocked
state, surrendering the CPU back to the schedule. When the I/O operation is completed, the process returns to the Ready
state until the scheduler decides to give it some more CPU time.
The notation used in this diagram is that of a UML state diagram. But the kinship to the formal model of FAs should be pretty obvious. The very fact that the notation for such state diagrams is part of an industry standard notation should give you a sense of how common it is for developers to use FAs as a basis for reasoning about software.
Pushing a bit further on this theme, model checking is an approach to validating complex systems, particularly concurrent systems. An FSA “specification” is given for various portions of a complex system. Combinations of states that are considered “dangerous” or that would indicate a failure (e.g., a system going into deadlock) are identified Analysis, some algorithmic and some human, of those state diagrams is performed to see if it is possible to reach any of those undesired states. The process for doing this is not unlike the process of minimizing the states of an FSA from section 2.6.