# CMPSCI 311: Theory of Algorithms

### from Wednesday 29 Oct 2003

Questions in black, answers in blue.

Remember that a DFA is a machine that reads a string and decides whether it is in a particular language by maintaining a state from a finite state set. Here is a Java class modeling DFA's whose input is strings over the alphabet {a,b}:

``````     public class Dfa
int k; // number of states
bool [] isFinal; // whether each state is final
int [] atrans, btrans; // transitions for a, b: atrans[i] is delta(a,i)
int state; //current state, note start state is always 0

public Dfa (int [] fin, int [] at, int [] bt)
{// creates new DFA with given final states, transformations
k = fin.length;
if ((at.length != k) || (bt.length != k))
throw new ArithmeticException ("DFA constructor : bad input");
isFinal = fin; atrans = at; btrans = bt;
state = 0;}

public boolean accept (String w)
{// returns true iff this DFA accepts w, w must be in {a,b}*
state = 0;
for (int i=0; i < w.length(); i++) {
if (w.charAt(i) == 'a') state = atrans[state];
else if (w.charAt(i) == 'b') state = btrans[state];
else throw new ArithmeticException("DFA: bad input string");}
return isFinal[state];}

public static void main (String[] args)
{// creates sample Dfa with three states, tests it
Dfa d = new Dfa({false, true, false}, {0,1,2}, {1,2,0});
System.out.println("It is " + d.accept("abbabbaa") +
" that abbabbaa is in the language");}
``````

• Question 1: What is the running time of `accept` on a string of length n, assuming that k is a constant? Explain your answer.

The method has a single loop that is executed n times on a string of length n. Inside the loop are Θ(1) operations and outside are Θ(1) more, so the total time is Θ(n).

• Question 2: For the DFA given in the main method, how many strings of length eight are in its language? (You may want to start by counting smaller strings.)

Many of you noticed that for this particular automaton, the behavior on a string depends only on the number of b's in the string. In particular, the string is accepted iff the number of b's is congruent to 1 modulo 3. So with n=8, the number of accepted strings is (8 choose 1) + (8 choose 4) + (8 choose 7) = 8 + (8*7*6*5)/(1*2*3*4) + 8 = 8 + 70 + 8 = 86.

There is another way to solve the problem that generalizes to the algorithm we use below for arbitary DFA's. As explained below, we need to raise a particular three by three matrix to the eighth power and read off an entry of the result. Using repeated squaring and ASCII art:

``````
| 1  1  0 |^8     | 1  2  1 |^4      | 5  5  6 |^2      | 85 86 85 |
| 0  1  1 |    =  | 1  1  2 |    =   | 6  5  5 |    =   | 85 85 86 |
| 1  0  1 |       | 2  1  1 |        | 5  6  5 |        | 86 85 85 |
``````

and the 0,1 entry is 86.

• Question 3: Write a method `census` for the `Dfa` class that takes an ```int parameter n and returns the number of strings of length n in the DFA's language. There is a slow way to do this by brute force, and a more clever way. Try to do both. Analyze the running time of your methods in terms of n, again assuming that k is a constant. The slow method is to cycle through all possible strings of length n, run the DFA on each, and count the number that are accepted. The easiest way I know to cycle through all strings is to translate each of the numbers from 0 through 2n-1 into a string by mapping the binary representation from over {0,1} to {a,b}: public static String stringNumber (int length, int number) {// returns number'th string of given length in {a,b}* String w = ""; for (int i = length - 1; i >= 0; i--) if (bit(number, i) == 0) w += "a"; else w += "b"; return w;} public int slowCensus (int n) {// counts accepted strings of length n by exhaustive search int count = 0; for (int i=0; i < power(2,n); i++) if (accept(stringNumner(n,i))) count++; return count;} Here "bit" and "power" are the obvious static methods to get a particular bit of a number or raise a number to a power. This algorithm takes Θ(n2n) time, since it executes 2n loops of Θ(n) time each. Here's a second method that one could easily discover without knowing anything about matrices. It's natural to ask whether a census function for length n helps you compute the census function for length n+1. It doesn't, because you have to know which state the n-letter prefix of the string wound up in before you can tell where it wound up. But if you keep an array telling how many strings wind up in each state, this can be used to get the answer: public int [] censusArray (int n) {// entry i is how many strings of length n take calling DFA to state i int [] result = new int[k]; //recall k is number of states if (n == 0) result[0] = 1; // rest of result stays all 0's else { int [] previous = censusArray(n-1); for (int i=0; i < k; i++) { result[atrans[i]] += previous[i]; result[btrans[i]] += previous[i];}} return result;} public int fasterCensus (int n) {// counts accepted strings of length n int [] ca = censusArray(n); int count = 0; for (int i=0; i < k; i++) if (isFinal(i)) count += ca[i]; return count; The time of fasterCensus is dominated by the single call to censusArray. The latter is recursive, making calls to depth n before reaching the base case. Each call to the program uses time Θ(k) = Θ(1), so the total time is Θ(n), a vast improvement over the first version. If A is the edge-count matrix for the graph underlying the DFA, this code is essentially computing A^i times the vector [1,0,...,0] for each i and passing the answer as censusArray(i). But of course we could compute this matrix power more quickly by repeated squaring. Our final version does this, making use of a Matrix class that contains the following methods (we'll just give the declarations here, meaning and code should be pretty obvious): public class Matrix {// stores a matrix of ints int rows, columns; int [][] entry = new int[rows][columns]; public Matrix (int r, int c); public int getEntry (int i, int j); public int setEntry (int i, int j, int newvalue); public Matrix multiply (Matrix m); public Matrix identity (int size); public Matrix power (int n) {// returns (this)^n, as in 6.6 or lecture of 29 October if (n == 0) return identity(k); if (n == 1) return this; Matrix halfpower = power(n/2); Matrix result = halfpower.multiply(halfpower); if (n%2 == 1) result = this.multiply(result); return result;} {//back to Dfa class... public Matrix a() {// returns Matrix counting edges in graph of this DFA Matrix result = new Matrix(k,k); for (int i=0; i < k; i++) { result.setentry(i,atrans[i],result.getEntry(i,atrans[i])++); result.setentry(i,btrans[i],result.getEntry(i,btrans[i])++);} return result;} public int census (int n) {// returns number of accepted strings of length n, // uses repeated squaring Matrix aToTheN = a().power(n); int count = 0; for (int i=0; i < k; i++) if (isFinal[i]) count += aToTheN.getEntry(0,i); return count;} Now the running time of census is dominated by the call to power. And this is Θ(log n), because we recurse to that depth and each call to the program uses time Θ(k3) for the matrix multiplication. Note that since k is a constant, so is k3 and so each call to multiply uses Θ(1) time. ```
``` Last modified 29 October 2003 ```