This is a file in the archives of the Stanford Encyclopedia of Philosophy.

# Computability and Complexity

First published Thu Jun 24, 2004; substantive revision Mon Jul 28, 2008

A mathematical problem is computable if it can be solved in principle by a computing device. Some common synonyms for "computable" are "solvable", "decidable", and "recursive". Hilbert believed that all mathematical problems were solvable, but in the 1930's Gödel, Turing, and Church showed that this is not the case. There is an extensive study and classification of which mathematical problems are computable and which are not. In addition, there is an extensive classification of computable problems into computational complexity classes according to how much computation — as a function of the size of the problem instance — is needed to answer that instance. It is striking how clearly, elegantly, and precisely these classifications have been drawn.

## 1. What can be computed in principle? Introduction and History

In the 1930's, well before there were computers, various mathematicians from around the world invented precise, independent definitions of what it means to be computable. Alonzo Church defined the Lambda calculus, Kurt Gödel defined Recursive functions, Stephen Kleene defined Formal systems, Markov defined what became known as Markov algorithms, Emil Post and Alan Turing defined abstract machines now known as Post machines and Turing machines.

Surprisingly, all of these models are exactly equivalent: anything computable in the lambda calculus is computable by a Turing machine and similarly for any other pairs of the above computational systems. After this was proved, Church expressed the belief that the intuitive notion of "computable in principle" is identical to the above precise notions. This belief, now called the "Church-Turing Thesis", is uniformly accepted by mathematicians.

Part of the impetus for the drive to codify what is computable came from the mathematician David Hilbert. Hilbert believed that all of mathematics could be precisely axiomatized. He felt that once this was done, there would be an "effective procedure", i.e., an algorithm that would take as input any precise mathematical statement, and, after a finite number of steps, decide whether the statement was true or false. Hilbert was asking for what would now be called a decision procedure for all of mathematics.

As a special case of this decision problem, Hilbert considered the validity problem for first-order logic. First-order logic is a mathematical language in which most mathematical statements can be formulated. Every statement in first-order logic has a precise meaning in every appropriate logical structure, i.e., it is true or false in each such structure. Those statements that are true in every appropriate structure are called valid. Those statements that are true in some structure are called satisfiable. Notice that a formula, Φ, is valid iff its negation, ¬Φ, is not satisfiable.

Hilbert called the validity problem for first-order logic, the entscheidungsproblem. In a textbook, Principles of Mathematical Logic by Hilbert and Ackermann, the authors wrote, "The Entscheidungsproblem is solved when we know a procedure that allows for any given logical expression to decide by finitely many operations its validity or satisfiability.… The entscheidungsproblem must be considered the main problem of mathematical logic." (Böerger, Grädel, & Gurevich 1997).

In his 1930 Ph.D. thesis, Gödel presented a complete axiomatization of first-order logic, based on the Principia Mathematica by Whitehead and Russell (Gödel 1930). Gödel proved his Completeness Theorem, namely that a formula is provable from the axioms if and only if it is valid. Gödel's Completeness theorem was a step towards the resolution of Hilbert's entscheidungsproblem.

In particular, since the axioms are easily recognizable, and rules of inference very simple, there is a mechanical procedure that can list out all proofs. Note that each line in a proof is either an axiom, or follows from previous lines by one of the simple rules. For any given string of characters, we can tell if it is a proof. Thus we can systematically list all strings of characters of length 1, 2, 3, and so on, and check whether each of these is a proof. If so, then we can add the proof's last line to our list of theorems. In this way, we can list out all theorems, i.e., exactly all the valid formulas of first-order logic, can be listed out by a simple mechanical procedure. More precisely, the set of valid formulas is the range of a computable function. In modern terminology we say that the set of valid formulas of first-order logic is recursively enumerable (r.e.).

Gödel's Completeness theorem was not sufficient, however, to give a positive solution to the entscheidungsproblem. Given a formula, Φ, if Φ is valid then the above procedure would eventually list it out and thus could answer, "Yes, Φ is valid." However, if Φ were not valid then we might never find this fact out. What was missing was a procedure to list out all the non-valid formulas, or equivalently to list out all satisfiable formulas.

A year later, in 1931, Gödel shocked the mathematical world by proving his Incompleteness Theorem: there is no complete and computable axiomatization of the first-order theory of the natural numbers. That is, there is no reasonable list of axioms from which we can prove exactly all true statements of number theory (Gödel 1931).

A few years later, Church and Turing independently proved that the entscheidungsproblem is unsolvable. Church did this by using the methods of Gödel's Incompleteness Theorem to show that the set of satisfiable formulas of first-order logic are not r.e., i.e., they cannot be systematically listed out by a function computable by the lambda calculus. Turing introduced his machines and proved many interesting theorems some of which we will discuss in the next section. In particular, he proved the unsolvability of the halting problem. He obtained the unsolvability of the entscheidungsproblem as a corollary.

Hilbert was very disappointed because his program towards a decision procedure for all of mathematics was proved impossible. However, as we will see in more detail in the rest of this article, a vast amount was learned about the fundamental nature of computation.

## 2. Turing Machines

In his 1936 paper, "On Computable Numbers, with an Application to the Entscheidungsproblem", Alan Turing introduced his machines and established their basic properties.

He thought clearly and abstractly about what it would mean for a machine to perform a computational task. Turing defined his machines to consist of the following:

• a finite set, Q, of possible states, because any device must be in one of finitely many possible states;
• a potentially infinite tape, consisting of consecutive cells, σ1, σ2, σ3, from some finite alphabet, Σ;
(Σ may be any finite set containing at least two symbols. It is convenient to fix Σ = {0, 1, b} consisting of the binary alphabet plus the blank cell symbol. We usually assume that a finite initial segment of the tape contains binary symbols, and the rest is blank.)
• a read/write tape head, h ≥ 1, scanning tape cell σh; and finally,
• a transition function, δ: Q × Σ → Q × Σ × {-1,0,1}.
(The meaning of the transition function is that from any given state, q, looking at any given symbol, σh, δ tells us the new state the machine should enter, the new symbol that should be written in the current square, and the new head position, h′ = h + d, where d ∈ {-1,0,1} is the displacement given by δ.)

The linear nature of its memory tape, as opposed to random access memory, is a limitation on computation speed but not power: a Turing machine can find any memory location, i.e., tape cell, but this may be time consuming because it has to move its head step by step along its tape.

The beauty of Turing machines is that the model is extremely simple, yet nonetheless, extremely powerful. A Turing machine has potentially infinite work space so that it can process arbitrarily large inputs, e.g., multiply two huge numbers, but it can only read or write a bounded amount of information, i.e., one symbol, per step. Even before Turing machines and all the other mathematical models of computation were proved equivalent, and before any statement of the Church-Turing thesis, Turing argued convincingly that his machines were as powerful as any possible computing device.

### 2.1 Universal Machines

Each Turing machine can be uniquely described by its transition table: for each state, q, and each symbol, σ, δ(q,σ) is the new state, the new symbol, and the head displacement. These transition tables, can be written as a finite string of symbols, giving the complete set of instructions of each Turing machine. Furthermore, these strings of symbols can be listed in lexicographic order as follows: M1, M2, M3, …, where Mi is the transition table, i.e., the complete set of instructions, for Turing machine number i. The transition table for Mi is the program for Turing machine i, or more simply, the ith program.

Turing showed that he could build a Turing machine, U, that was universal, in the sense that it could run the program of any other Turing machine. More explicitly, for any i, and any input w, U on inputs i and w would do exactly what Mi would do on input w, in symbols,

U(i,w)   =   Mi(w)

Turing's construction of a universal machine gives the most fundamental insight into computation: one machine can run any program whatsoever. No matter what computational tasks we may need to perform in the future, a single machine can perform them all. This is the insight that makes it feasible to build and sell computers. One computer can run any program. We don't need to buy a new computer every time we have a new problem to solve. Of course, in the age of personal computers, this fact is such a basic assumption that it may be difficult to step back and appreciate it.

### 2.2 The Halting Problem

Because they were designed to embody all possible computations, Turing machines have an inescapable flaw: some Turing machines on certain inputs never halt. Some Turing machines do not halt for silly reasons, for example, we can mis-program a Turing machine so that it gets into a tight loop, for example, in state 17 looking at a 1 it might go to state 17, write a 1 and displace its head by 0. Slightly less silly, we can reach a blank symbol, having only blank symbols to the right, and yet keep staying in the same state, moving one step to the right, and looking for a "1". Both of those cases of non-halting could be easily detected and repaired by a decent compiler. However, consider the Turing machine MF, which on input "0", systematically searches for the first counter-example to Fermat's last theorem, and upon finding it outputs the counter-example and halts. Until Andrew Wiles relatively recently proved Fermat's Last Theorem, all the mathematicians in the world, working for over three centuries, were unable to decide whether or not MF on input "0" eventually halts. Now we know that it never does.

### 2.3 Computable Functions and Enumerability

Since a Turing machine might not halt on certain inputs, we have to be careful in how we define functions computable by Turing machines. Let the natural numbers, N, be the set {0,1,2,…} and let us consider Turing machines as partial functions from N to N.

Let M be a Turing machine and n a natural number. We say that M 's tape contains the number n, if M 's tape begins with a binary representation of the number n (with no unnecessary leading 0's) followed by just blank symbols from there on.

If we start the Turing machine M on a tape containing n and it eventually halts with its tape containing m, then we say that M on input n, computes m: M(n) = m. If, when we start M on input n, it either never halts, or when it halts, its tape does not contain a natural number, e.g., because it has leading 0's, or digits interspersed with blank symbols, then we say that M(n) is undefined, in symbols: M(n) = . We can thus associate with each Turing machine, M, a partial function, M: NN ∪ {}. We say that the function M is total if for all nN, M(n) ∈ N, i.e., M(n) is always defined.

Now we can formally define what it means for a set to be recursively enumerable (r.e.) which we earlier described informally. Let SN. Then S is r.e. if and only if there is some Turing machine, M, such that S is the image of the function computed by M, in symbols,

S   =  {M(n) | nN; M(n) ≠ } .

Thus, S is r.e. just if it can be listed out by some Turing machine. Suppose that S is r.e. and its elements are enumerated by Turing machine M as above. We can then describe another Turing machine, P, which, on input n, runs M in a round-robin fashion on all its possible inputs until eventually M outputs n. If this happens then P halts and outputs "1", i.e., P(n)=1. If nS, then M will never output n, so P(n) will never halt, i.e., P(n)= .

Let the notation P(n)↓ mean that Turing machine P on input n eventually halts. For a Turing machine, P, define L(P), the set accepted by P, to be those numbers n such that P on input n eventually halts,

L(P)   =   {n | P(n)↓} .

The above argument shows that if a set S is r.e. then it is accepted by some Turing machine, P, i.e., S = L(P). The converse of this statement holds as well. That is, S is r.e. if and only if it is accepted by some Turing machine, P.

We say that a set, S, is decidable if and only if there is a total Turing machine, M, that decides for all nN whether or not nS. Think of "1" as "yes" and "0" as "no". For all nN, if nS, then M(n) = 1, i.e., M on input n eventually halts and outputs "yes", whereas if nN, then M(n) = 0, i.e., M on input n eventually halts and outputs "no". Synonyms for decidable are: computable, solvable, and recursive.

For SN, the complement of S is NS, i.e., the set of all natural numbers not in S. We say that the set S is co-r.e. if and only if its complement is r.e. If a set, S, is r.e. and co-r.e. then we can list out all of its elements in one column and we can list out all of its non-elements in a second column. In this way we can decide whether or not a given element, n, is in S: just scan the two columns and wait for n to show up. If it shows up in the first column then nS. Otherwise it will show up in the second column and nS. In fact, a set is recursive iff it is r.e. and co-r.e.

### 2.4 The Unsolvability of the Halting Problem

Turing asked whether every set of natural numbers is decidable. It is easy to see that the answer is, "no", by the following counting argument. There are uncountably many subsets of N, but since there are only countably many Turing machines, there can be only countably many decidable sets. Thus almost all sets are undecidable.

Turing actually constructed a non-decidable set. As we will see, he did this using a diagonal argument. The diagonal argument goes back to Georg Cantor who used it to show that the real numbers are uncountable. Gödel used a similar diagonal argument in his proof of the Incompleteness Theorem in which he constructed a sentence, J, in number theory whose meaning could be understood to be, "J is not a theorem."

Turing constructed a diagonal halting set, K, as follows:

K   =   {n | Mn(n)↓} .

That is, K consists of those Turing machines that eventually halt when input their own programs.

It is not hard to see that K is r.e. Suppose for the sake of a contradiction that K is also co-r.e., and let d be the number of a Turing machine that accepts the complement of K. That is, for any n,

nK   ⇔   Md (n)↓ .

But consider what happens when we substitute d for n in the above equation:

dK   ⇔   Md (d)↓ .

However, the definition of K tells us that:

dK   ⇔   Md (d)↓ .

Thus we have that

dK   ⇔   dK,

which is a contradiction. Thus our assumption that K is co-r.e. is false. Thus K is not recursive. It follows that it is not a computable problem to be given a Turing machine and its input and to decide whether or not the Turing machine will eventually halt on that input, i.e., the halting problem is unsolvable.

## 3. Primitive Recursive Functions

We next define the class of Primitive Recursive Functions. This is a very interesting class of functions defined by Gödel as part of his proof of the Incompleteness Theorem. We are interested in functions f from Nr to N, for r = 0, 1, 2, … . Here r is called the arity of the function f, i.e., the number of arguments that it takes. Gödel started with three very simple functions, the initial functions, and two natural closure operations, composition and primitive recursion, each of which take some already defined functions and use them to define a new one. We next explain his definitions in detail. This section is technical and can be safely skipped. The important idea is that the primitive recursive functions comprise a very large and powerful class of computable functions, all generated in an extremely simple way.

We begin with the three initial primitive recursive functions:

• ζ, the zero function of arity 0, ζ( ) = 0;
• η, the identity function of arity 1, η(n) = n; and,
• σ, the successor function of arity 1, σ(n) = n +1.

Now consider the following two operations:

• Composition: if f is a primitive recursive function of arity a, and g1, …, ga are primitive recursive functions of arities r1, …, ra, and kN, then the following is a primitive recursive function of arity k:
h(x1, …, xk) = f (g1(w1), …, ga(wa)),

where each wi is a list of ri arguments, perhaps with repetition, from x1, …, xk; and,

• Primitive recursion: if f and g are primitive recursive functions of arity k and k+2, respectively, then there is a primitive recursive function, h, of arity k+1 satisfying the following conditions:
• h(0,x1,…,xk) = f (x1,…,xk); and,
• h(n+1,x1,…,xk) = g(h(n,x1,…,xk), n,x1,…,xk).

Here composition is the natural way to combine functions, and primitive recursion is a restricted kind of recursion in which h with first argument n+1 is defined in terms of h with first argument n, and all the other arguments unchanged.

Define the primitive recursive functions to be the smallest class of functions that contains the Initial functions and is closed under Composition and Primitive Recursion. The set of primitive recursive functions is equal to the set of functions computed using bounded iteration (Meyer & Ritchie 1967), i.e. the set of functions definable in the language Bloop from (Hofstadter 1979).

The primitive recursive functions have a very simple definition and yet they are extremely powerful. Gödel proved inductively that every primitive recursive function can be simply represented in first-order number theory. He then used the primitive recursive functions to encode formulas and even sequences of formulas by numbers. He finally used the primitive recursive functions to compute properties of the represented formulas including that a formula was well formed, a sequence of formulas was a proof, and that a formula was a theorem.

It takes a long series of lemmas to show how powerful the primitive recursive functions are. The following are a few examples showing that addition, multiplication, and exponentiation are primitive recursive.

Define the addition function, P(x,y), as follows:

• P(0,y) = η(y)
• P(n+1,y) = σ(P(n,y))

(Note that this fits into the definition of primitive recursion because the function g(x1,x2,x3) = η(σ(x1)) is definable from the initial functions η and σ by composition.)

Next, define the multiplication function, T(x,y), as follows:

• T(0,y) = ζ( )
• T(n+1,y) = P(T(n,y),y)

Next, we define the exponential function, E(x,y). (Usually 00 is considered undefined, but since primitive recursive functions must be total, we define E(0,0) to be 1.) Since primitive recursion only allows us to recurse on the first argument, we use two steps to define the exponential function:

• R(0,y) = σ(ζ( ))
• R(n+1,y) = T(R(n,y),y)

Finally we can define E(x,y) = R(η(y),η(x)) by composition. (Recall that η is the identity function so this could be more simply written as E(x,y) = R(y,x).)

The exponential function, E, grows very rapidly, for example, E(10,10) is ten billion, and E(50,50) is over 1084 (and thus significantly more than the estimated number of atoms in the universe). However, there are much faster growing primitive recursive functions. As we saw, E was defined from the slowly growing function, σ, using three applications of primitive recursion: one for addition, one for multiplication, and then one more for exponentiation. We can continue to apply primitive recursion to build a series of unimaginably fast growing functions. Let's do just one more step in the series defining the hyper-exponential function, H(n,m) as 2 to the 2 to the 2 to the … to the m, with a tower of n 2's. H is primitive recursive because it can be defined from E using one more application of primitive recursion:

• H(0,y) = y
• H(n+1,y) = E(2,H(n,y))

Thus H(2,2) = 24 = 16, H(3,3) = 2256 is more than 1077 and comparable to the number of atoms in the universe. If that's not big enough for you then consider H(4,4). To write this number in decimal notation we would need a one, followed by more zero's than the number of particles in the universe.

### 3.1 Recursive Functions

The set of primitive recursive functions is a huge class of computable functions. In fact, they can be characterized as the set of functions computable in time that is some primitive recursive function of n, where n is the length of the input. For example, since H(n,n) is a primitive recursive function, the primitive recursive functions include all of TIME[H(n,n)]. (See the next section for a discussion of computational complexity, including TIME.) Thus, the primitive recursive functions include all functions that are feasibly computable by any conceivable measure of feasible, and much beyond that.

However, the primitive recursive functions do not include all functions computable in principle. To see this, we can again use diagonalization. We can systematically encode all definitions of primitive recursive functions of arity 1, calling them p1, p2, p3, and so on.

We can then build a Turing machine to compute the value of the following diagonal function, D(n) = pn(n) + 1.

Notice that D is a total, computable function from N to N, but it is not primitive recursive. Why? Suppose for the sake of a contradiction that D were primitive recursive. Then D would be equal to pd for some dN. But it would then follow that

pd(d) = pd (d) +1,

which is a contradiction. Therefore, D is not primitive recursive.

Alas, the above diagonal argument works on any class of total functions that could be considered a candidate for the class of all computable functions. The only way around this, if we want all functions computable in principle, not just in practice, is to add some kind of unbounded search operation. This is what Gödel did to extend the primitive recursive functions to the recursive functions.

Define the unbounded minimization operator, μ, as follows. Let f be a perhaps partial function of arity k+1. Then μ[f] is defined as the following function of arity k. On input x1, …, xk do the following:

For i = 0 to ∞ do {

if f(i,x1,…,xk) = 1, then output i

}

Thus if f (i,x1,…,xk) = 1, and for all j < i, f (j,x1,…,xk) is defined, but not equal to 1, then μ [f ](x1, …, xk) = i. Otherwise μ[f ](x1, …, xk) is undefined.

Gödel defined the set of Recursive functions to be the closure of the initial primitive recursive functions under composition, primitive recursion, and μ . With this definition, the Recursive functions are exactly the same as the set of partial functions computable by the Lambda calculus, by Kleene Formal systems, by Markov algorithms, by Post machines, and by Turing machines.

## 4. Computational Complexity: Functions Computable in Practice

During World War II, Turing helped design and build a specialized computing device called the Bombe at Bletchley Park. He used the Bombe to crack the German "Enigma" code, greatly aiding the Allied cause [Hodges, 1992]. By the 1960's computers were widely available in industry and at universities. As algorithms were developed to solve myriad problems, some mathematicians and scientists began to classify algorithms according to their efficiency and to search for best algorithms for certain problems. This was the beginning of the modern theory of computation.

In this section we are dealing with complexity instead of computability, and all the Turing machines that we consider will halt on all their inputs. Rather than accepting by halting, we will assume that a Turing machine accepts by outputting "1" and rejects by outputting "0", thus we redefine the set accepted by a total machine, M,

L(M)   =   {n | M(n) = 1} .

The time that an algorithm takes depends on the input and the machine on which it is run. The first important insight in complexity theory is that a good measure of the complexity of an algorithm is its asymptotic worst-case complexity as a function of the size of the input, n.

For an input, w, let n = |w| be the length of w. Following [Hartmanis, 1989] we say that a Turing machine M runs in time T(n) if for all w of length n, M(w) takes at most T(n) steps and then halts. This is called worst-case complexity because T(n) must be as large as the time taken by any input of length n.

For any function T : NN, let

TIME[T(n)]   =   { A | A = L(M) for some M that runs in time T(n)}

Alan Cobham and Jack Edmonds identified the complexity class, P, of problems recognizable in some polynomial amount of time, as being an excellent mathematical wrapper of the class of feasible problems — those problems all of whose moderately-sized instances can be feasibly recognized,

P   =   i=1,2,… TIME[ni ]

Any problem not in P is certainly not feasible. On the other hand, natural problems that have algorithms in P, tend to eventually have algorithms discovered for them that are actually feasible.

Many important complexity classes besides P have been defined and studied; a few of these are NP, PSPACE, and EXPTIME. PSPACE consists of those problems solvable using some polynomial amount of memory space. EXPTIME is the set of problems solvable in time 2p(n) for some polynomial, p.

Perhaps the most interesting of the above classes is NP: nondeterministic polynomial time. The definition comes not from a real machine, but rather a mathematical abstraction. A nondeterministic Turing machine, N, makes a choice (guess) of one of two possible actions at each step. If, on input w, some sequence of these choices leads to acceptance, then we say that N accepts w, and we say the nondeterministic time taken by N on input w, is just the number of steps taken in the sequence that leads to acceptance. A nondeterministic machine is not charged for all the other possible choices it might have made, just the single sequence of correct choices.

NP is sometimes described as the set of problems, S, that have short proofs of membership. For example, suppose we are given a list of m large natural numbers: a1, …, am, and a target number, t. This is an instance of the Subset Sum problem: is there a subset of the m numbers whose sum is exactly t? This problem is easy to solve in nondeterministic linear time: for each i, we guess whether or not to take ai. Next we add up all the numbers we decided to take and if the sum is equal to t then accept. Thus the nondeterministic time is linear, i.e., some constant times the length of the input, n. However there is no known (deterministic) way to solve this problem in time less than exponential in n.

There has been a large study of algorithms and the complexity of many important problems is well understood. In particular reductions between problems have been defined and used to compare the relative difficulty of two problems. Intuitively, we say that A is reducible to B (AB) if there is a simple transformation, τ, that maps instances of A to instances of B in a way that preserves membership, i.e., τ(w) ∈ B ⇔ w ∈ A.

Remarkably, a high percentage of naturally occurring computational problems turn out to be complete for one of the above classes. (A problem, A, is complete for a complexity class C if A is a member of C and all other problems B in C are no harder than A, i.e., BA. Two complete problems for the same class have equivalent complexity.)

The reason for this completeness phenomenon has not been adequately explained. One plausible explanation is that natural computational problems tend to be universal in the sense of Turing's universal machine. A universal problem in a certain complexity class can simulate any other problem in that class. The reason that the class NP is so well studied is that a large number of important practical problems are NP complete, including Subset Sum. None of these problems is known to have an algorithm that is faster than exponential time, although some NP-complete problems admit feasible approximations to their solutions.

A great deal remains open about computational complexity. We know that strictly more of a particular computational resource lets us solve strictly harder problems, e.g. TIME[n] is strictly contained in TIME[n1.01] and similarly for SPACE and other measures. However, the trade-offs between different computational resources is still quite poorly understood. It is obvious that P is contained in NP. Furthermore, NP is contained in PSPACE because in PSPACE we can systematically try every single branch of an NP computation, reusing space for the successive branches, and accepting if any of these branches lead to acceptance. PSPACE is contained in EXPTIME because if a PSPACE machine takes more than exponential time, then it has exactly repeated some configuration so it must be in an infinite loop. The following are the known relationships between the above classes:

P   ⊆   NP   ⊆   PSPACE   ⊆   EXPTIME

However, while it seems clear that P is strictly contained in NP, that NP is strictly contained in PSPACE, and that PSPACE is strictly contained in EXPTIME, none of these inequalities has been proved. In fact, it is not even known that P is different from PSPACE, nor that NP is different from EXPTIME. The only known proper inclusion from the above is that P is strictly contained in EXPTIME. The remaining questions concerning the relative power of different computational resources are fundamental unsolved problems in the theory of computation.

## Bibliography

• Church, Alonzo, 1933, "A Set of Postulates for the Foundation of Logic (Second Paper)", Annals of Mathematics, second series, 33, Princeton, 839-864.
• Church, Alonzo, 1936, "An Unsolvable Problem of Elementary Number Theory," American Journal of Mathematics, 58, Baltimore, 345-363..
• Church, Alonzo, 1936, "A Note on the Entscheidungsproblem," Journal of Symbolic Logic, 1, 40-41; correction 1, Menasha, Wis., 101-102.
• Böerger, Egon, Erich Grädel, and Yuri Gurevich, 1997, The Classical Decision Problem, Springer, Heidelberg.
• Cobham, Alan, 1964, "The Intrinsic Computational Difficulty of Functions," Proceedings of the 1964 Congress for Logic, Mathematics, and Philosophy of Science, North-Holland, Amsterdam, 24-30.
• Cook, Stephen, 1971, "The Complexity of Theorem Proving Procedures," Proceedings of the Third Annual ACM STOC Symposium, Shaker Heights, Ohio, 151-158.
• Davis, Martin, 2000,The Universal Computer: the Road from Leibniz to Turing, W. W. Norton & Company, New York.
• Edmonds, Jack, 1965, "Paths, Trees and Flowers", Canadian Journal of Mathematics, 17, Toronto, 449-467.
• Enderton, Herbert B., 1972, A Mathematical Introduction to Logic, Academic Press, New York.
• Garey, Michael and David S. Johnson, 1979, Computers and Intractability,, Freeman, New York.
• Gödel, Kurt 1930, "The Completeness of the Axioms of the Functional Calculus," in (van Heijenoort, 1967), 582-591.
• Gödel, Kurt 1931, "On Formally Undecidable Propositions of Principia Mathematica and Related Systems I," in (van Heijenoort, 1967), 592-617.
• Hartmanis, Juris, 1989, "Overview of computational Complexity Theory" in J. Hartmanis, ed., Computational Complexity Theory, American Mathematical Society, Providence, 1-17.
• Hilbert and Ackermann, 1928/1938, Grundzüge der theoretischen Logik, Springer. English translation of the 2nd edition: Principles of Mathematical Logic, 1950, Chelsea Publishing Company, New York.
• Hodges, Andrew, 1992, Alan Turing: the Enigma, Random House, London.
• Hofstadter, Douglas, 1979, Gödel, Escher, Bach: an Eternal Golden Braid, Basic Books, New York.
• Hopcroft, John E., 1984, "Turing Machines", Scientific American, 250(5), New York, 70-80.
• Karp, Richard, "Reducibility Among Combinatorial Problems", in Complexity of Computations, R.E. Miller and J.W. Thatcher, eds. (1972), Plenum Press, New York, 85-104.
• Kleene, Stephen C., 1935, "A Theory of Positive Integers in Formal Logic," American Journal of Mathematics, 57, Baltimore, 153-173, 219-244.
• Kleene, Stephen C., 1950, Introduction to Metamathematics, Van Nostrand, Princeton.
• Levin, Leonid, 1973, "Universal search problems," Problemy Peredachi Informatsii, 9(3), 265-266; partial English translation in: B.A.Trakhtenbrot, 1984, "A Survey of Russian Approaches to Perebor (Brute-force Search) Algorithms," IEEE Annals of the History of Computing, 6(4), Los Alamitos, CA, 384-400.
• Markov, A.A., 1960, "The Theory of Algorithms," American Mathematical Society Translations, series 2, 15, Providence, 1-14.
• Meyer, Albert and Dennis Ritchie, 1967, "The Complexity of Loop Programs." Proc. 22nd National ACM Conference, Washington, DC, 465-470.