Lecture 3
In-class notes: CS 505 Spring 2025 Lecture 3
Undecidability Wrap-up
We begin by wrapping up our discussion of undecidability.
The Halting Problem
From last time, we’ll finish proving that the halting problem is undecidable. First, recall the definition of the halting problem.
Theorem 2.7. is undecidable.
Proof. We’ll prove this via a reduction to the language from last lecture, defined as
Our proof will be by contradiction. In particular, this means we’ll assume that is decidable, then derive our contradiction by giving a decider for .
Thus assume that is decidable. This means there is a Turing machine which decides . This tells us that for every pair , we have if and only if halts, and if and only if does not halt.
We’ll use to build a Turing machine which decides . For any , define as follows.
-
- Set .
- If , then output 1.
- If , then set .
- If , output 1.
- If , output 0.
Since is a decider, it halts on all possible inputs . Now, if , we know that does not halt, which implies that . So we set in this case. Next, if , we know that does halt. We then test the output of by running it. If , then again we know , so we set . Otherwise, , and thus , so we set .
Thus, halts on all possible inputs, and clearly decides . This contradicts our previous result that is undecidable. Therefore, is undecidable.
Final Remarks on Undecidability
Rice’s Theorem
It would be great if the halting problem were decidable, as it would give us an efficient way to check if programs halt on all possible inputs. However, one may be wondering if there are other properties about programs we can efficiently decide/test. For example, “does this program have at least 5 for-loops?” or “does this program have a switch statement, followed by an if-then-else?” Unfortunately, these are also undecidable problems.
This is a result known as Rice’s Theorem, which informally states that it is impossible to determine if a computer program has any non-trivial property . I.e., the language is undecidable. Here, a non-trivial property is a property which is not true or false for every program (i.e., there are some programs that satisfy , and some which do not).
Mathematical Incompleteness
The idea of undecidability (and uncomputability) is closely related to (and inspired by) Gödel’s incompleteness theorem. In the early 1900’s, there was a large push to establish a set of mathematical aximoms from which you can prove or disprove any mathematical property. However, Gödel proved this is impossible. He showed that no matter what set of axioms you choose, there will always be theorems you cannot prove or disprove. This actually inspired the results on undecidability/uncomputability, and is closely related to these ideas.
Time-Efficient Computations
We’ll now turn our focus to a central topic in complexity theory: defining classes of efficient computations. This leads us to defining and discussing various complexity classes. Informally, a complexity class is simply a set of languages which are decidable (resp., computable) within some resource bound. Example resource bounds include running in linear time, running in logarithmic space, etc.
Deterministic Time
Building towards what we as computer scientists consider efficient, we turn to time bounds. We’ll define the notion of deterministic time.
Definition. Let be a function. A language is in the class DTIME if and only if is decidable by a (deterministic) Turing machine in time .
All Turing machines we’ve discussed and defined so far have been deterministic. These machines all have straight-line computations: they execute their transition function, which simply outputs the next state. Later, we’ll see non-deterministic Turing machines, where the transition function can output a set of possible states and the Turing machine non-deterministically decides which state to pick next.
The Complexity Class P
Given the definition above, we can now define the set of (what we consider to be) all efficient computations. This is the complexity class P (which stands for polynomial).
Definition (P)
We consider anything computed in polynomial time (with respect to the input length) to be efficient. Examples of problems/languages in P include:
- Graph connectivity
- Digraph path exists
- Checking if a graph is a tree
- Integer multiplication: does ?
- Are the integers and relatively prime?
- Gaussian elimination over rational numbers: For matrix and vector , does there exist such that ?
Discussions on P
Does the computational model matter?
We’ve defined P with respect to -tape Turing machines. But, as we’ve seen, -tape Turing machines are equivalent to all other Turing machine models we’ve seen, including RAM Turing machines which reasonably emulate real-life computers. Moreover, the “equivalence” here is that all machines can simulate all other ones with at most polynomial overhead in the runtime. This means all of these computations still fall within the class P.
In fact, many people believe that Turing machines can simulate any physically realizable computational model or system. This is known as the Church-Turing thesis.1 Some people also believe in the strong Church-Turing thesis, which states that this simulation can be done with only polynomial overhead in the runtime. However, as we get closer to quantum computing being physically realizable, people may stop believing in this since, for now, we do not know of a way to simulate quantum computations on standard Turing machines with only polynomial overhead.
Why polynomial time?
It is certainly true that an algorithm running in time is impractical starting at ; yet this is a polynomial. Why do we consider all polynomial time algorithms to be “efficient?”
One reason is above: the Turing machine is polynomially-equivalent to pretty much every model we have thought of, so it makes sense that polynomial time should appear somewhere in what we consider to be an efficient computation. Polynomials also compose well, which emulates how we compose computer programs. Often, computer programs will run sub-routines, and will run routines one after another. If all these runtimes are polynomial, then the final runtime remains polynomial as well. This is since for two polynomials and , the functions , , and or are all still polynomials.
Another reason is historic and heuristic. Often in history, someone is able to solve a problem in polynomial time, but for some large polynomial like . But this algorithm is later improved to a more reasonable polynomial, such as or .
Finally, polynomial-time problems are roughly equivalent to most (if not all) problems that we can efficiently solve on modern computers.
Worst-case time complexity is too restrictive
If you have a problem where for of the inputs you have an algorithm, but for you have an algorithm, then we’d say the algorithm runs in time . In particular, we keep P as a worst-case class. Some argue that this is too restrictive, which is valid. However, often it is much simpler to construct an algorithm that can solve all problem inputs in some amount of time, rather than trying to enumerate (the possibly infinite amount of) the inputs which have better algorithms.
This criticism of P is also addressed in complexity theory itself via the introduction of alternative models and classes, including approxmiation algorithms and average-case complexity.
Decision problems are too limited
We’ve framed P as a class of decision problems, but often we actually want to find solutions to these problems. This is known as a search problem, where you are asked to find an answer rather than decide if something is true or false. An example of this is: instead of deciding if there exists an such that , you just compute the solution . It can also be difficult to frame search problems as decision problems in the first place.
However, most often it is the case that the difference between search and decision problems is, again, only polynomial. That is, we often can solve a search problem when given an algorithm that decides the equivalent decision problem, only costing us polynomial overhead in the runtime; the reverse is often true as well.
Time-Efficient Verification of Problems
Sometimes, we don’t want to solve problems, but would like to verify solutions when given an answer. Moreover, this verification should at least as efficient as solving the problem itself.
Suppose we are given a large integer and would like to find the prime factors of , which we denote as . We believe it to be difficult to find given just . However, if someone gives you some numbers which are claimed to be the prime factors of , there is a simple and efficient algorithm to verify this is true.
- Check that each is prime.
- Check that .
Clearly (2) is efficient, only requiring integer multiplications. A relatively recent result showed that (1) is also efficient and doable in polynomial-time. So verifying that is the product of is also efficient.
Efficiently Verifiable Languages
This gives us a new way to define languages: efficiently verifiable languages.
Definition. Let be a language. We say that is efficiently verifiable if there exists polynomials and , and Turing machine running in time such that
In the above definition, we call a verifier, the instance, and the certificate or witness.
The Class NP
The above new notion of languages gives us a new complexity class: NP.
Definition (NP).
P vs. NP
We widely believe that . In fact, we build many systems (e.g., cryptography) based on the above assumption. Resolving this either way is one of the [Millennium Prize Problems](https://www.claymath.org/millennium-problems/).However, we do know one thing for certain.
Theorem 3.1. .
This is true since every problem in can be decided in polynomial time with no witness/certificate. So it meets the definition of efficiently verifiable.
Non-deterministic Turing Machines and NP
There is an alternative definition of the class , which utilizes non-deterministic Turing machines.
Definition. A non-deterministic -tape Turing machine is identical to a (deterministic) -tape Turing machine, except for the following modifications.
- The transition function of the non-deterministic Turing machine is defined as , where denotes the power set operation.2 During any step of the computation, the transition function outputs a (possibly empty) list of next possible Turing machine configurations.
- Given a list of next possible configuration from the transition function, the non-deterministic Turing machine non-deterministically chooses the next configuration to execute from this list.3
Intuitively, deterministic Turing machines (the ones we defined in Lecture 1) are “straight-line”: every step of the computation proceeds directly from the previous one. For non-deterministic Turing machines (which we’ll denote as NTMs), they look more like “branching” programs: at every step of the computation, the Turing machine has a set of possible computational paths to head down, and non-deterministically chooses the path to proceed down.
How do we define decidability of a language with respect to NTMs? At first, it may seem difficult since there are many possible paths an NTM can do down during its computation. But, the answer turns out to be simple: we require all computational paths to halt, and there to be at least one accepting path (out of possibly exponential) which correctly outputs the decision.
Definition. A language is decidable in time by a non-deterministic Turing machine if
- if and only if there exists at least one execution path such that .
- All execution branches halt in time at most for any .
We can use this above definition to expand DTIME to NTIME.
Definition. Let be a function. Then we define to be the set of all languages decidable by an NTM running in time .
Alternative Definition of NP
Given NTMs and NTIME, we can now see the original formulation of the class NP.
Theorem 3.2.
Note that this definition is equivalent to the efficiently verifiable language definition. At a high level, this is because of the following reduction.
- Let be a witness to the fact that (i.e., for efficient verifier ). Then, intuitively, correpsonds to some correct computational path on an NTM which decides .
- Let be an NTM which decides . Then we can specify a witness which is the computational path that takes to an accepting state. The deterministic machine takes this as input and simulates the NTM by following the computational path specified by .
Recall our prime factor problem from before. Let be a large integer, and suppose we wish to find the prime factors of . Then there is an extremely simple NTM which finds these prime factors. Let be this machine. It does the following.
- Non-deterministically choose prime numbers .
- Check if . If yes, output ; else output .
Solving NTIME in DTIME
Currently, until vs. is resolved, the most efficient ways that we know of to solve problems in NTIME using only DTIME computations requires exponential time. Let denote the class
Lemma 3.3. .
Proof. Enumerate all possible branches of the NTM deciding the language (equivalently, enumerate all certificates/witnesses in the verifier definition). Then, run through this list until finding an accepting branch of the computation. If the original machine ran in time , then this procedure runs in time . By assumption, is a polynomial, so we are done.
-
Note this is just a belief and not a formal theorem or conjecture. ↩
-
Given a set , the power set of , denoted as , is the set of all possible subsets of . Notably, . ↩
-
Recall that non-determinism is not the same as behaving randomly. The choice of a non-deterministic machine is arbitrary and possibly not computable. ↩