Computability
(Content adapted from Critchlow & Eck)
At this point, it would be useful to look at increasingly complex Turing machines, which compute increasingly complex functions and languages. Although Turing machines are very simple devices, it turns out that they can perform very sophisticated computations. In fact, any computation that can be carried out by a modern digital computer—even one with an unlimited amount of memory—can be carried out by a Turing machine. Although it is not something that can be proved, it is widely believed that anything that can reasonably be called "computation" can be done by a Turing machine. This claim is known as the Church-Turing Thesis.
We do not have time to look at enough examples to convince you that Turing machines are as powerful as computers, but the proof reduces to the fact that computers are actually fairly simple in their basic operation. Everything that a computer does comes down to copying data from one place to another, making simple comparisons between two pieces of data, and performing some basic arithmetic operations. It's possible for Turing machines to do all these things. In fact, it's possible to build a Turing machine to simulate the step-by-step operation of a given computer. Doing so proves that the Turing machine can do any computation that the computer could do, although it will, of course, work much, much more slowly.
Extended Turing Machines
We can, however, look briefly at some other models of computation and see how they compare with Turing machines. For example, there are various ways in which we might try to increase the power of a Turing machine. For example, consider a two-tape Turing machine that has two tapes, with a read/write head on each tape. In each step of its computation, a two-tape Turing machine reads the symbols under its read/write heads on both tapes. Based on these symbols and on its current state, it can write a new symbol onto each tape, independently move the read/write head on each tape one cell to the left or right, and change state.
It might seem that with two tapes available, two-tape Turing machines might be able to do computations that are impossible for ordinary one-tape machines. In fact, though, this is not the case. The reason, again, is simulation: Given any two-tape Turing machine, it is possible to build a one-tape Turing machine that simulates the step-by-step computation of the two-tape machine. Let be a two-tape Turing machine. To simulate with a one-tape machine, , we must store the contents of both of 's tapes on one tape, and we must keep track of the positions of both of 's read/write heads. Let @ and $ be symbols that are not in the alphabet of . The @ will be used to mark the position of a read/write head, and the $ will be used to delimit the parts of 's tape that represent the two tapes of . For example, suppose that one of 's tapes contains the symbols "abb##cca" with the read/write head on the first b, and that the other tape contains "01#111#001" with the read/write head on the final 1. This configuration would be represented on 's tape as "$a@bb##cca$01#111#00@1$". To simulate one step of 's computation, must scan its entire tape, looking for the @'s and noting the symbol to the right of each @. Based on this information, can update its tape and its own state to reflect 's new configuration after one step of computation. Obviously, will take more steps than and it will operate much more slowly, but this argument makes it clear that one-tape Turing machines can do anything that can be done by two-tape machines.
We needn't stop there. We can imagine -tape Turing machines, for . We might allow a Turing machine to have multiple read/write heads that move independently on each tape. We could even allow two or three-dimensional tapes. None of this makes any difference as far as computational power goes, since each type of Turing machine can simulate any of the other types.1
Recursively Enumerable Languages
We have used Turing machines to define Turing-acceptable languages and Turing-decidable languages. The definitions seem to depend very much on the peculiarities of Turing machines. But the same classes of languages can be defined in other ways. For example, we could use programs running on an idealized computer, with an unlimited amount of memory, to accept or decide languages. Or we could use -tape Turing machines. The resulting classes of languages would be exactly the same as the Turing-acceptable and Turing-decidable languages.
We could look at other ways of specifying languages "computationally." One of the most natural is to imagine a Turing machine or computer program that runs forever and outputs an infinite list of strings over some alphabet . In the case of Turing machines, it's convenient to think of a two-tape Turing machine that lists the strings on its second tape. The strings in the list form a language over . A language that can be listed in this way is said to be recursively enumerable. Note that we make no assumption that the strings must be listed in any particular order, and we allow the same string to appear in the output any number of times. Clearly, a recursively enumerable language is "computable" in some sense. Perhaps we have found a new type of computable language. But no—it turns out that we have just found another way of describing the Turing-acceptable languages. The following theorem makes this fact official and adds one more way of describing the same class of languages:
Theorem: Let be an alphabet and let be a language over . Then the following are equivalent:
- There is a Turing machine that accepts .
- There is a two-tape Turing machine that runs forever, making a list of strings on its second tape, such that a string is in the list if and only if .
- There is a Turing-computable function such that is the range of the function .
While I will not give a complete, formal proof of this theorem, it's not too hard to see why it is true. Consider a language that satisfies the third property. We can use the fact that is the range of a Turing-computable function, , to build a two-tape Turing machine that lists . The machine will consider each of the strings , for , in turn, and it will compute for each . Once the value of has been computed, it can be copied onto the machine's second tape, and the machine can move on to do the same with . This machine writes all the elements of (the range of ) onto its second tape, so satisfies the second property. Conversely, suppose that there is a two-tape Turing machine, , that lists . Define a function such that for , is the item in the list produced by . Then the range of is , and is Turing-computable since it can be computed as follows: On input , simulate the computation of until it has produced strings, then halt, giving the string as output. This shows that the second property implies the third property, so these properties are in fact equivalent.
We can also check that the second property is equivalent to the first. Suppose that satisfies the second property. Consider a two-tape Turing machine, , that lists the elements of . We must build a Turing machine, , which accepts . We do this as follows: Given an input , will simulate the computation of . Every time the simulated produces a string in the list, compares that string to . If they are the same, halts. If , eventually it will be produced by , so will eventually halt. If , then it will never turn up in the list produced by , so will never halt. Thus, accepts the language . This shows that the second property implies the first one.
The fact that the first property implies the second is somewhat harder to see. First, we note that it is possible for a Turing machine to generate every possible string in , one-by-one, in some definite order (such as order of increasing length, with something like alphabetical order for strings of the same length). Now, suppose that is Turing-acceptable and that is a Turing machine that accepts . We need a two-tape Turing machine, that makes a list of all the elements of . Unfortunately, the following idea does not work: Generate each of the elements in in turn, and see whether accepts it. If so, then add it to the list on the second tape. It looks like we have a machine that lists all the elements of . The problem is that the only way for to "see whether accepts" a string is to simulate the computation of . Unfortunately, as soon as we try this for any string that is not in , the computation never ends! will get stuck in the simulation and will never even move on to the next string. To avoid this problem, must simulate multiple computations of at the same time. can keep track of these computations in different regions of its first tape (separated by $'s). Let the list of all strings in be , , , …. Then should operate as follows:
1: Set up the simulation of on input , and simulate one step of the computation for
2: Set up the simulation of on input , and simulate one step of the computation for and one step of the computation for .
3: Set up the simulation of on input , and simulate one step of each of the computations, for , , and .
…
n: Set up the simulation of on input , and simulate one step of each of the computations, for , , \dots, .
and so on. Each time one of the computations halts, should write the corresponding onto its second tape. Over the course of time, simulates the computation of for each input for an arbitrary number of steps. If , the simulated computation for will eventually end and it will appear on 's second tape. On the other hand, if , then the simulated computation will never end, so will not appear in the list. So we see that does in fact make a list of all the elements, and only the elements of . This completes an outline of the proof of the theorem.
General Grammars
Next, we compare Turing machines to a completely different method of specifying languages: general grammars. Suppose is a general grammar and that is the language generated by . Then there is a Turing machine, , that accepts the same language, . The alphabet for will be , where $ is a symbol that is not in . (We also assume that # is not in .) Suppose that is started with input , where . We have to design so that it will halt if and only if . The idea is to have find each string that can be derived from the start symbol . The strings will be written to 's tape and separated by $'s. can begin by writing the start symbol, , on its tape, separated from by a $. Then it repeats the following process indefinitely: For each string on the tape and for each production rule, , of , search the string for occurrences of . When one is found, add a $ to the end of the tape and copy the string to the end of the tape, replacing the occurrence of by . The new string represents the results of applying the production rule to the string. Each time produces a new string, it compares that string to . If they are equal, then halts. If is in fact in , then eventually will produce the string and will halt. Conversely, if is not in , then will go on producing strings forever without ever finding , so will never halt. This shows that, in fact, the language is accepted by .
Conversely, suppose that is a language over an alphabet , and that is Turing-acceptable. Then it is possible to find a grammar that generates . To do this, it's convenient to use the fact that, as discussed above, there is a Turing-computable function such that is the range of . Let be a Turing machine that computes the function . We can build a grammar, , that imitates the computations performed by . The idea is that most of the production rules of will imitate steps in the computation of . Some additional rules are added to get things started, to clean up, and to otherwise bridge the conceptual gap between grammars and Turing machines.
The terminal symbols of will be the symbols from the alphabet, . For the non-terminal symbols, we use: the states of , every member of that is not in , two special symbols and , and two additional symbols and . (We can assume that all these symbols are distinct.) will be the start symbol of . As for production rules, we begin with the following three rules:
These rules make it possible to produce any string of the form . This is the only role that and play in the grammar. Once we've gotten rid of and , strings of the remaining terminal and non-terminal symbols represent configurations of the Turing machine . The string will contain exactly one of the states of (which is, remember, one of the non-terminal symbols of ). This tells us which state is in. The position of the state-symbol tells us where is positioned on the tape: the state-symbol is located in the string to the left of the symbol on which is positioned. And the special symbols and just represent the beginning and the end of a portion of the tape of . So, the initial string represents a configuration in which is in its start state, and is positioned on the first in a string of 's. This is the starting configuration of when it is run with input .
Now, we need some production rules that will allow the grammar to simulate the computations performed by . For each state and each symbol , we need a production rule that imitates the transition rule . If , that is if the machine moves to the right, then all we need is the rule
This represents that fact that converts the to a , moves to the right, and changes to state . If , that is if the machine moves to the left, then we will need several rules—one rule for each , namely
This rule says that changes the to a , moves left, and changes to state . The doesn't affect the application of the rule, but is necessary to represent the fact that moves left.
Each application of one of these rules represents one step in the computation of . There is one remaining requirement for correctly simulating . Since 's tape contains an infinite number of cells and we are only representing a finite portion of that tape, we need a way to add and remove #'s at the ends of the string. We can use the following four rules to do this:
These rules allow blank symbols to appear at the ends of the string when they are needed to continue the computation, and to disappear from the ends of the string whenever we like.
Now, suppose that is some element of . Then for some . We know that on input , halts with output . If we translate the computation of into the corresponding sequence of production rules in , we see that for the grammar , , where is the halt state of . Since we already know that , for every , we see that in fact for each . We almost have it! We want to show that . If we can just get rid of the , the , and the , we will have that and we can then deduce that for each , as desired. We can do this by adding just a few more rules to . We want to let the eliminate the , move through the , and then eliminate the along with itself. We need the rules
and, for each ,
We have constructed so that it generates every string in . It is not difficult to see that the strings in are in fact the only strings that are generated by . That is, is precisely .
We have now shown, somewhat informally, that a language is Turing-acceptable if and only if there is a grammar that generates . Even though Turing machines and grammars are very different things, they are equivalent in terms of their ability to describe languages. We state this as a theorem:
Theorem: A language is Turing acceptable (equivalently, recursively enumerable) if and only if there is a general grammar that generates .
Turing Decidability
In this section, we have been talking mostly about recursively enumerable languages (also known as the Turing-acceptable languages). What about the Turing-decidable languages? We already know that if a language is Turing-decidable, then it is Turing-acceptable. The converse is not true (although we won't be able to prove this until the next section). However, suppose that is a language over the alphabet and that both and its complement, , are Turing-acceptable. Then is Turing-decidable.
For suppose that is a Turing machine that accepts the language and that is a Turing machine that accepts . We must show that is Turing-decidable. That is, we have to build a Turing machine that decides . For each , when is run with input , it should halt with output 1 if and with output if . To do this, will simulate the computation of both and on input . (It will simulate one step in the computation of , then one step in the computation of , then one step of , then one step of , and so on.) If and when the simulated computation of halts, then will halt with output 1; since accepts , this will happen if and only if . If and when the simulated computation of halts, then will halt with output 0; since accepts , this will happen if and only if . So, for any , halts with the desired output. This means that does in fact decide the language .
It is easy to prove the converse, and the proof is left as an exercise. So we see that a language is Turing-decidable if and only if both it and its complement are Turing-acceptable. Since Turing-acceptability can be defined using other forms of computation besides Turing machines, so can Turing-decidability. For example, a language is Turing-decidable if and only if both it and its complement can be generated by general grammars. We introduced the term "recursively enumerable" as a synonym for Turing-acceptable, to get away from the association with a particular form of computation. Similarly, we define the term "recursive" as a synonym for Turing-decidable. That is, a language is said to be recursive if and only if it is Turing-decidable. We then have the theorem:
Theorem: Let be an alphabet and let be a language over . Then is recursive if and only if both and its complement, , are recursively enumerable.
Exercises
The language is the range of the function . Design a Turing machine that computes this function, and find the grammar that generates the language by imitating the computation of that machine.
Complete the proof of the above theorem by proving the following: If is a recursive language over an alphabet , then both and are recursively enumerable.
Show that a language over an alphabet is recursive if and only if there are grammars and such that the language generated by is and the language generated by is .
This section discusses recursive languages and recursively enumerable languages. How could one define recursive subsets of and recursively enumerable subsets of ?
Give an informal argument to show that a subset is recursive if and only if there is a computer program that prints out the elements of in increasing order.
- We can also define non-deterministic Turing machines that can have several possible actions at each step. Non-deterministic Turing machines cannot be used to compute functions, since a function can have only one possible output for any given input. However, they can be used to accept languages. We say that a non-deterministic Turing machine accepts a language is it is possible for the machine to halt on input if and only if . The class of languages accepted by non-deterministic Turing machines is the same as the class of languages accepted by deterministic Turing machines. So, non-determinism does not add any computational power.↩