5.1. Computable Problems

To demonstrate that a problem is computable, all you need is an algorithm for it. For example, take the language

PRIME = {n | n is a prime integer}
where the alphabet is the set of decimal digits. (An integer n is prime if n > 1 and the only factors of n are 1 and n.)

Any algorithm will do. Here is a sketch of one.

  isprime(n)
    if n < 2 then
      return false
    else
      for k = 2,...,n-1 do
        if n mod k = 0 then
          return false
        end if
      end for
      return true
    end if

Some really easy problems

Suppose

E1 = {3}
Is E computable? Sure! Here is an algorithm that solves it.
  inE(n)
  if n == 3 then
    answer yes
  else
    answer no
  end if

What about E2 = {2,4}? Sure!.

  inE2(n)
  if n == 2 or n == 4 then
    answer yes
  else
    answer no
  end if

Is {} computable? Sure! The answer is always no.

  inEmpty(n)
  Answer no


A question about finite state machines

We will get a lot of useful results out of problems that ask questions about programs. To start, let's concentrate on finite state machines.

It is easy to come up with a textual description of finite state machines. For example, list the alphabet, the set of states, the start state, the accepting states and a table giving the transition function.

M is the a textual description of finite state machine M.

For our first problem about finite state machines, lets use

ACCEPT-DFA = {(‹M›, s) | M accepts string s}
(An ordered pair can also be encoded in text; I just did it in the line above.)

You should immediately realize that ACCEPT-DFA is computable. Just use the characters of s to follow transitions in M. Writing a program to do that is just a trivial programming exercise.


NONEMPTY-DFA

Define language

NONEMPTY-DFA = {‹M› | M accepts at least one string}
To solve NONEMPTY-DFA, it is just a matter of finding out if any accepting state is accessible by a sequence of transitions from the start state.

  nonempty(‹M›)
    markAllAccessible(‹M›)
    If any accepting state of M has a mark in it then
      return true
    else
      return false
    endif

  markAllAccessible(‹M›)
    put a mark in the start state of M.
    finished = false.

    while not finished do
      finished = true.
      for each state q of M
        if q has a mark in it
          for every state q' where there 
              is a transition from q to q'
            if q' does not have a mark in it
              put a mark in state q'.
              finished = false.
            end if
          end for
        end if
      end for
    end while

That is all it takes to show that NONEMPTY-DFA is computable (plus, of course, an argument that the algorithm works, which I leave to you.)


ALL-DFA

Define language

ALL-DFA = {‹M› | M accepts all strings over its alphabet}
Think for a moment about how you would solve ALL-DFA. Here is a spurious argument that ALL-DFA is not computable.

To ask whether ‹M› ∈ ALL-DFA, you try every string s, and check whether M accepts s.

But there are infinitely many strings to check! So the algorithm never stops. Since an algorithm needs to stop for us to say that it solves a problem, ALL-DFA must not be computable.

Nonsense! That is the naive argument: "I thought of an approach that does not work, therefore no approach will work."

In fact, ALL-DFA is computable, and it is not really very difficult.

Start by marking all states of M that are accessible from the start state, as in the preceding algorithm.

If, after all accessible states have been marked, you find that all marked states are accepting states, then the answer is true, M ∈ ALL-DFA.

If there is an accessible state that is rejecting, then the answer is false.