Pushdown Automata

Reading

Section 2.2

Practice problems (page 128): 2.7, 2.10, 2.11, 2.12, 2.13, 2.18

Challenge: 2.20, 2.25

Pushdown Automata

Pushdown automata are also some times called “stack machines”. They are an extension of finite automata, where we are allowed a “stack” to store information. We should start with a quick reminder on stacks:

Stacks

A stack is a structure on which you can do exactly two things:

A stack in our usage does not allow you to see what elements are under the top element.

Stacks start their “life” empty, and grow and shrink in size as we push and pop elements in them. The key thing to remember is the LIFO behavior: The last thing in is the first thing out.

In OCAML we could implement a stack by simply using a list, where new elements are prepended to the list and popping removes the head of the list and keeps the tail.

Pushdown Automata

The idea of pushdown automata is similar to that of finite automata:

So formally, a pushdown automaton is defined thus:

A pushdown automaton is a 6-tuple \((Q,\Sigma,\Gamma, \delta, q_0, F)\) where:

Note that we only look at nondeterministic pushdown automata. Deterministic pushdown automata are NOT equivalent to them, but we will not consider them here. Essentially, because of the presence of the stack, we cannot do away with non-determinism the way we did with NFAs.

Computation in a pushdown automaton goes as follows:

Computation in a pushdown automaton:

The language of the automaton is the set of all strings that the automaton accepts.

For example consider the language:

\[L = \left\{ x^ny^m \mid n \geq m \geq 0 \right\}\]

So the language allows any number of \(x\)’s followed by no more than that many \(y\)’s. Here is an automaton that will recognize the language. We will use the stack to count how many \(x\)’s we have seen that have not been matched by corresponding \(y\)’s.

Here is a visual representation:

A simple pushdown automaton
A simple pushdown automaton

Now let us try a slightly more difficult language:

\[L = \left\{ x^ny^n \mid n \geq 0 \right\}\]

This is a bit different than the previous example. In the previous example we did not need to know that the stack was empty before returning. In fact there might be a bunch of \(x\)’s left in the stack. All we had to make sure is that we don’t add a \(y\) unless there was an \(x\) to match it.

This case is different. We will need to know when we have taken all the \(x\)’s out of the stack, because it is only at that time that they are matched to the \(y\)’s. But by default there isn’t really an automatic mechanism for doing that. But we can build one ourselves, and from now on we will do these steps without thinking about them much:

How to check for empty stack:

Let us illustrate this with a pushdown automaton for the language \(L\) described above:

Matching x’s to y’s
Matching \(x\)’s to \(y\)’s

Exercise 1: Work out a PDA for the language that consists of all palindromes.

Exercise 2: Work out a PDA for the language that consists of all matched parentheses (e.g. (()(()()))).

Exercise 3: Work out a PDA for the language that consists of an equal number of \(x\)s and \(y\)s (but not requiring all \(x\)s before all \(y\)s).

Emptying the stack

There are some convenient things we can do to simplify a PDA, that will be critical for what we will do next. First of all, we can of course assume that there is only one accept state, by instead \(\epsilon\)-transitioning to a new state from all the accept states like we would do in an NFA. But the nice thing in our case is that we can also empty the stack first:

For any PDA there is an equivalent PDA which only accepts with an empty stack.

The process for constructing this new PDA is somewhat simple: