|
|
Issue 2, February 2004
REVIEW: Engineering & Applied Sciences
The Limits of Computation
Anthony Widjaja
Melbourne University
Discuss this article!
Abstract
We study the limits of computation from a complexity theory standpoint
by consid¬ering some incomputable problems and intractable problems.
Of practical interest is the NP-complete complexity class, because
it contains a wealth of practical problems. However, it is unknown
to date whether an efficient algorithm for solving them exists.
This question has in fact been classified as one of the seven most
important open prob¬lems of this century by the Clay Mathematics
Institute. This review discusses some of the most striking consequences
of this question in science and life, mentions some evidence of
why the answer to this question may be negative, and discusses some
approaches researchers have taken for resolving this conjecture.
Since the article is written for a broad audience, some basic concepts
from computer science and mathematics are reviewed.
Introduction
Computers, unarguably, are one of the greatest achievements of
humans’ endeavor. Their presence has made a significant impact
on the history of mankind — many things once found only in
science fiction novels have been made a reality. This raises the
question: are there limitations to what computers can do? Some negative
answers have been identified in many subfields of computer science.
In particular, one can find a plethora of negative results in the
field of computational complexity. Therefore, we shall investigate
the question from a computational complexity perspective.
Computational
complexity — one of the oldest branches of computer science
— provides a theory on what is not possible (or feasible)
for computers to compute. The theory asserts that some problems,
such as the halting problem, are incomputable in a technical sense.
Furthermore, even if we restrict ourselves to computable problems,
there may still be problems whose solutions take too long to compute,
perhaps longer than the age of our universe, even on the best supercomputer
available. The most important class of such problems is called NP-complete,
which contains numerous practical problems such as the traveling
salesman problem, the channel assignment problem, the exam timetabling
problem, and the graph coloring problem. The exam timetabling problem,
for instance, asks for a conflict-free final exam timetable that
minimizes the exam period for a given set of students and a given
set of finals. Of course, conflicts arise because a student may
take more than one course in a semester. Clearly, this problem is
of practical interest, and will be further addressed later in this
article.
Even
though NP-complete problems are hard in the sense that no efficient
algorithm for them is known to date, this does not necessarily mean
that one does not exist. In fact, they are open questions that have
not been resolved since NP-complete problems were first identified
more than 30 years ago (Cook 1971; Levin 1973). The significance
of the NP-complete question is indisputable: the Clay Mathematics
Institute classifies it as one of the seven most important open
problems of the century (together with the famous Poincaré
Conjecture, for instance) and promises a reward of $1,000,000 to
the first person who provides a logically correct answer (Clay Institute
2003). On the one hand, a positive answer to this question (i.e.,
that NP-complete problems are computable) will have a positive effect
in life, since many practical problems will be able to be solved
efficiently. On the other hand, a negative answer to this question
imposes a limit of our current model of computation, in which case
we may need to look for other ways to solve NP-complete problems,
such as searching for approximation algorithms or investigating
new models of computation, like quantum and DNA computing.
This article is intended to give a glimpse of the limits of computation
to a broad audience. Prerequisite computing knowledge is kept to
a minimum; however, a familiarity with basic concepts from mathematics
is required. The first section will fix some notations and ter¬minology
that will be used throughout the article. The second section will
present a concrete incomputable problem. The third section is the
heart of the article, where we will see that computability does
not imply tractability (i.e., some problems, albeit computable,
may require a consider¬able amount of time to compute). In particular,
in this section we shall study a number of practical NP-complete
problems (along with some ways of attacking them), and discuss the
aforementioned famous open question. Finally the article will be
concluded in the fourth section. Because some basic notions from
graph theory will be used in the third section, readers unfamiliar
with the subject will find the information in Appendix A useful.
What
are Algorithms? What are Problems?
We
can think of a deterministic
algorithm (or
just algorithm) as a precise recipe, procedure,
or method that takes an input, carries out the prescribed procedure,
and spills an output. Each step is carried out sequentially. Equivalently,
an algorithm can also be thought of as a C program that takes an
input from the user, carries out the program, and outputs the final
result of computation onto the screen.
An
algorithm is usually used to compute a problem. So, we have to define
what the problem is. Mathematically, we can think of a problem f as a mapping
f : I → O, where I is a set of
possible input strings and O a set of possible output strings.
To be useful at all, any algorithm A that computes
f must necessarily terminate for any given input i ≤ I and outputs f(i). We denote the output of the algorithm
on input i ≤ I by A(i).
In
particular, there is an important class of problems: the decision
problems. A problem f : I → O is
said to be a decision
problem if
O = {“yes”, “no”}. We usually denote a “yes” by 1 and a “no” by 0.
For notational convenience, we may think of the decision problem
f as the set of inputs i such that f(i) = 1. So, whenever suitable, we
shall write i ≤ f to mean that
i is an input of f that
satisfies f(i) = 1. Unfortunately, there are much more functions than
there are algorithms. In effect, as we shall see later, there are
problems which are incomputable;
in other words,
there is no algorithm that can compute them.
Before
going any further, let us see a concrete algorithm that computes
a simple decision problem. Consider the problem EVEN of determining whether a given natural number is even. We formulate it
as follows:
Problem: EVEN
Input: a natural number n
Output: 1 if n is even, 0 otherwise
We
can describe an algorithm A that computes EVEN as follows:
A = “On input n where n is a natural
number:
1. If two
divides n, then output 1
2. Otherwise, output 0”
First,
it is important to note that right after an algorithm gives an output,
it must terminate. Now, we see that this algorithm works correctly
since a number n is either even or odd and that, by
definition, an even number is one that is divisible by two.
Let
us now contemplate a slightly harder problem: the problem PRIME of determining whether a number n is prime. We formulate it below:
Problem: PRIME
Input: a natural number n
Output: 1 if n is prime, 0 otherwise
The
following simple algorithm computes PRIME:
B = “On input n where n is a natural
number:
1. If n = 1 or n = 0, output
0
2. For i = 2 to n −1, do the following:
3. If i divides n, then output 0
4. Output 1”
In
programming, the For statement in the algorithm B basically means: go through the body of the For statement (i.e., the lines indented) with i = 2 for the first loop, i = 3 for the second loop, i = 4 for the third loop, and i = n − 1
for the last loop. Now, to see that B computes PRIME, first observe that, by definition, a prime number n is a number other than 1 and 0
that is only divisible by 1 and the number n itself.
Step 1 takes care of the inputs 0 and 1, since they are not prime. Step 2 ensures that if there is a number
between 2
and n −1 that divides n, the algorithm will give a 0 since
n is not prime. After doing the checking B is required to do, we are assured that n satisfies the definition of prime and thus output
1. Unfortunately, this simple algorithm is inefficient;
see Agrawal
et al. (2002) for an efficient algorithm.
An Incomputable Problem
Before
we discuss NP-complete problems, it will be instructive
to find whether there are incomputable problems. After all, the
existence of such problems creates an inherent limitation in computers
in terms of computability. While our argument in the previous
section does not really demonstrate a concrete incomputable
problem, we shall encounter one incomputable problem in this section:
the famous Halting Problem.
The
Halting Problem was first formulated by Alan Turing, one of the
pioneers of computer science, at the time he proposed his model
of computation: the Turing machine. This problem actually came
as a shock, since it was first conjectured that for every function
there exists an algorithm — it is just a matter of when we discover
it. We define the Halting Problem, calling it HALT, as follows:
Problem: HALT
Input: Program A and an input I for A
Output: 1 if A terminates on input I, 0
otherwise
Now,
why is this problem incomputable? After all, can’t we just write
a program that simulates A on input I and outputs
whatever A outputs? This is not the case; for if the input program
A does not terminate, then neither will the program
that simulates it — failing to compute HALT.
I shall only mention that this problem is simply incomputable.
However, the readers should be aware that this non-trivial result
can be rigorously proved using some diagonalization
arguments.
Such a proof has been nicely done by, for example, Sipser (1997).
Hard Computable Problems
Even
if we restrict our attention exclusively to computable problems,
there may still be problems that require significant amounts
of time to compute. We shall see some in this section. But, first,
how do we determine if a problem is hard? How can we tell whether an algorithm performs well?
Do we just plug
in an
input, run the algorithm, and count how many seconds before it
terminates? To answer these questions, we shall discuss some techniques
to measure the efficiency of algorithms. Two important complexity
classes, P and NP, will then be introduced. After
that we shall encounter the famous open question of P vs. NP and
discuss some recent progress toward answering this question. Then
we will see a number of ways of attacking NP-complete
problems. Finally, I shall end this section by mentioning some
impacts of the question of P
vs. NP in practical life.
Asymptotic
analysis of algorithms
Computer
scientists have developed a general way of measuring the efficiency
(i.e., the time complexity or the running time) of an algorithm
without actually running the algorithm on some inputs. After
all, we want to have an efficiency measure of an algorithm independent of the computers on which we run
it. We use a technique called asymptotic analysis, first introduced for analyzing the time complexity of an algorithm
by renowned computer scientist Donald Knuth. Basically, this technique
tries to mimic how fast the time, which an algorithm uses to solve
a problem, grows with the size of its input using some mathematical
functions. However, if we were to analyze it in a precise manner,
then undoubtedly the functions could be very complicated. This
is when asymptotic analysis becomes useful.
In
particular, asymptotic analysis provides a means of approximating complicated functions using some
very simple functions. For example, suppose that we have algorithms
A and B whose running
times are, respectively, 1000n2 and 2n where n is the size
(length) of the input strings. Although the algorithm B runs faster than A does
for small n, the algorithm A clearly outperforms B for large n. To make this notion more precise, we need the following definitions:
Definition
1
(The “big-O notation”): Let f and g be two functions
f, g : N → N. We say that f(n) = O(g(n)) if there exists positive constants
c and n0 such
that for all n > n0, f(n) → cg(n). We say that g is an upper bound for f. Equivalently,
we also say that f is of order
g. See Figure 1.
|
| Figure
1 . An
illustration of the big-O notation. Here, we have f(n) =
O(g(n)), and g(n) = Ω(f(n)).
We say that the growth of function g(n) is asymptotically
faster than the grown of function f(n). That is, after n0,
g(n) is always greater than f(n).
|
Definition
2
(The “big-omega notation”): Let
f and g be two functions
as above. We say that f(n) = Ω(g(n)) if g(n) = O(f(n)). We say that g is a lower bound for f.
In
essence, these notations give us a way of saying that some function
f is bounded from above (or below) by some function
g. One thing to note is that the constant
of a function is immaterial in an asymptotic analysis. For instance,
1000n2
and 100000n2 are both of order 2. As another example,
we have 10000000 = O(1).
Moreover,
these notations allow us to disregards terms of smaller order.
Consider the function f(n) = 1000n2 + 4n3 + 8n as an example. Clearly, n3 dominates the growth rate of this
function for large n, and, thus, we say that f(n) = O(n3). One last example: the function
O(n4) + O(n) + O(2n) is of order
2n
.
We also write this O(n4) + O(n) + O(2n)=
O(2n). All these can be
shown with some rigorous arguments starting from the definition.
Here are some simple, but useful, functions that we can use for
approximating complicated functions:
f(n) = 1: a constant function. As we saw before, for any positive
constant c,
we have c = O(1).
f(n) = log(n): a logarithmic function.
f(n) = n: a linear function.
f(n) = n2: a quadratic function.
f(n) = nk: a polynomial function.
f(n) = 2n : an exponential function.
f(n) = n! : a
factorial function.
How
does this measure relate to real computers? Table 1 shows how
long it takes for algorithms whose time complexity is f(n) to carry out a task (on a computer on which each operation
takes 10-9 seconds). We see that, to begin with, when n = 10, all of these algorithms take about the same
amount of time. However, for n = 20, the
algorithms whose time complexity is O(n!) become impractical, meaning it
takes quite a few years before it finishes. For n > 40, the algorithm whose running
time is O(2n) becomes impractical.
Well before n = 50,
the value of n! exceeds the number of atoms in
the universe: 1070 (Gershenfeld
2000).
There
are some handy facts to know when analyzing algorithms. First,
arithmetic operations (+,
−, ×, /) can be
executed in constant time (i.e., they require O(1) steps). Second, comparison operators
also require constant time. Third, an algorithm spends most of
its time repeating the same behavior with different
parameters (i.e., looping using the For or
While
statements).
Equipped
with this knowledge, let us now try analyzing the time complexity
of the algorithm A that computes the EVEN algorithm introduced above. Step 1 requires constant time,
since divisions can be done in constant time. Outputting 1 or 0 can also
be done in constant time. Hence, the algorithm A runs in O(1) + O(1) = O(1) time. Note that the algorithm
A has no looping statement.
Now,
let us turn our attention to analyzing the algorithm B that computes PRIME. Steps 1 and 4 require constant time. The algorithm repeats step 3 n times. Hence, steps 2 and 3 take O(n) time. In total, the algorithm takes O(n) time. Recall that the complexity of an algorithm is expressed
in terms of the size of its input. Let m be the size of the input n. Since n is represented in binary digits in computers, m is the length of the input string n in binary form, which is logarithmic in n. More precisely, m = log2(n) and so n = 2m.
So, B indeed runs in time O(2m). Note that
a similar argument also shows that B runs in time
Ω(2m).
Complexity
theorists have shown that an algorithm whose running time is O(nk) for k constant (a
polynomial-time algorithm) is strictly more efficient than one whose running time is Ω(2n) (an exponential-time algorithm). But what
if the number k in the function O(nk) is large, e.g., 1000? Then the algorithm becomes impractical.
However, once there is a polynomial-time algorithm for a given
problem, a significant insight into the problem is gained and
usually faster algorithms (algorithms with running time O(nk) for small k) follow.
The
Complexity Class P
The
complexity class P
is defined to be the set of decision
problems for which there exists a polynomial-time algorithm. Said
differently, P
is the class of decision problems
that computers can compute efficiently.
From
our analysis of the algorithm A, for
example, we see that EVEN
is in
P (is efficient). On the other hand, our analysis
of the algorithm B does not show
that PRIME
is in P (neither does it show that PRIME is not in P) since B runs in exponential
time. Recently, Agrawal et al. (2002) showed
that PRIME
is in P — a nontrivial result that has spawned massive interests
in theoretical computer science.
Another
interesting problem in P
is the
following problem from graph theory, called PATH (see Appendix
A for a discussion of graph theory).
Problem: PATH
Input: Graph G = (V, E) and two
vertices u, v in V
Output: 1 if there is a path connecting u and v
in G, 0 otherwise
Observe
that, in practice, PATH
may be used to
model the problem of determining whether there exists a sequence
of flights that can take passenger from an airport a to an airport
b. Now, to show that PATH is in P, we need to give a polynomial-time algorithm that
computes it. The following simple algorithm C computes
PATH:
C =
“On input G = (V, E)
and u, v:
1. Mark the vertex u.
2. Repeat until the following marks no additional vertices:
3. Scan all edges in E. If an edge (a, b) is found where a is marked
and b unmarked, then mark b.
4. Output 1 if v is marked and 0 otherwise.”
It
is not hard to see that this algorithm computes PATH.
Now, analyzing its complexity, we see that the bottleneck computation
is located in steps 2 and 3. The time spent here is bounded above
by the number of edges in G which is O(n2) where n = |V| is
the size of the input. Hence, this algorithm runs in time O(n2). So, PATH is in P.
The
Complexity Class NP
The
class NP
is the set of computable decision
problems for which the answer "yes,” accompanied by an evidence
for the answer, called a witness, can always be verified in polynomial-time.
To get a feel for the concept of witness, observe that there are
many problems (in particular, puzzles) in life for which it seems
very hard to find an answer; but, once a “witness” is provided,
it seems trivial to check the answer.
It
is a fact that P NP (Sipser
1997). For example, PATH
is in
NP. That is, given an input <G, u, v> for PATH, a witness for <G, u, v>
P NP PATH is basically a path P in G connecting the two vertices u and v. That P is indeed such a path can be verified in polynomial
time. Basically, the verifier needs to check that every edge in
P is a valid edge in G and that P actually connects
u and v. The running time of this algorithm is bounded from above by the number
of edges in G, which is in the worst case O(n2) where n = |V|.
A
real-world example of NP
problems
is the channel assignment problem, which
is currently a
heavily researched topic (e.g., Roberts 1991; Griggs and
Yeh 1992; Whittlesey et al. 1995; Chang and Kuo
1996; Bodlaender
et al. 2000).
As is the case with many combinatorial problems, this problem
is quite simple to state. Given a set of transmitters x1, ..., xn in
a region, one wishes to assign a channel (frequency) f(xi) for the transmitter xi for 1 < i < n such that interference is avoided as much as possible.
This problem often arises in radio telecommunication.
We
shall now study a particular graph-theoretic model of this problem
called the L(2,1)-labeling
problem. The region in the problem directly translates to a graph
G whose vertices are the transmitters x1, ..., xn. We say that two vertices xi, xj, interfere if the distance between xi and
xj is at most two. A channel assignment f is a function mapping x1, ..., xn to a set of natural numbers corresponding
to channels such that, for any two vertices x, y, we have | f(x) −
f(y) |
> 1
if x and y are adjacent
and |
f(x) −
f(y) |
> 2
if x and y are a distance two apart. We define
the span
of f, denoted span(f), to be the difference between the maximum and the minimum channels
used in f. Also, f is said to be optimal for a graph G if there is no channel assignment f ’ such
that span(f ’) < span(f). The channel assignment problem CAP is defined as follows:
Problem: CAP
Input:
Graph G = (V, E)
Output:
An optimal channel assignment for G
Figure
2 illustrates a sample input and output to CAP. Clearly, CAP is
not a decision problem; it is actually called an optimization problem since it attempts to optimize
a function. Therefore, to show that CAP is in NP,
we need to reformulate this as a decision problem. Given a graph
G, let l(G) be the minimum span taken over all possible channel assignment appropriate
for G. Now, we may translate CAP to the following decision problem CAP(D) as follows:
Problem: CAP(D)
Input:
Graph G = (V, E)
Output:
1 if l(G) < |V|,
0 otherwise
|
| Figure
2. An optimal channel assignment f for a cycle
with six vertices. The numbers next to the vertices correspond
to channels. Here, span(f) = 4.
|
First,
observe that CAP(D) is no harder than CAP;
if there is a polynomial-time algorithm for CAP,
then there is also a polynomial-time algorithm for CAP(D) (indeed, this argument generalizes to any optimization problem). That
is, by showing that CAP(D) is hard, we also show CAP is hard. (We shall discuss what is hard in the next section.) Now, to show
that CAP(D) is in NP, we need
to show that CAP(D) is computable and that, given a witness for an input G =(V, E),
we can check if G CAP(D) in polynomial-time. It can be shown that l(G) < (n2 − n) where n = |V| (Chang
and Kuo 1996). So, we can basically enumerate every possible function
f : V(G) →{0, . . ., n2 − n} and output 1 if there is a channel assignment f with span(f) < |V|(or
output 0 otherwise). Since the number of such functions is finite,
CAP(D) is computable. If, instead, G P NP CAP(D), a witness for it is simply a valid
channel assignment that satisfies l(G) < |V|. Also, we
can verify this in polynomial-time (since the verifier is similar
to that of PATH). Hence, CAP(D) is in NP.
Now,
I shall illustrate another problem of a slightly different flavor. Recall the exam timetabling problem referenced in the
Introduction. The problem is to assign a time slot to each final
exam, while minimizing the exam period. Moreover, since some students
may take more than one courses in a given semester but only one
exam during any given time slot, some possible conflicts may arise.
We
start by discussing a graph-theoretic model of this problem. How
do we translate the problem to the language of graph theory? First,
let the set V(G) of vertices in the graph G be the set of final exams. For any two final exams
u, v in V(G), we draw an edge (u, v)
P NP E(G) if and only if there is a student
who will be sitting in both exams u and
v. Now the problem reduces to the
problem of vertex-coloring (or graph-coloring): we wish to assign
a color (time slot) to each vertex such that no two adjacent vertices
receive the same color. It is easy to see that these two problems
are equivalent. Now, let c(G) be the smallest number of colors needed to color G such that no two adjacent vertices receive the same
color. Then the exam timetabling problem EXAM can be stated as follows:
Problem: EXAM
Input: Graph G representing final exams and conflicts
Output: An optimal coloring for G and the number c(G)
See
Figure 3 for a sample input and output to EXAM. Of course, as before, EXAM is not a decision problem. At
this stage, the reader may enjoy devising a decision problem corresponding
to EXAM and show that, like CAP(D), it is NP.
|
| Figure
3. A sample conflict graph G. Vertices of G
are the exams to be scheduled in a given semester (typically,
in a real university setting, there can be 500 exams per
semester). Edges represent conflict. Colors represent time
slots. Here, we have χ(G) = 3.
|
P
vs. NP: The Million-Dollar Question
We
have just shown that CAP(D) is in NP, but is
CAP(D) in P? Surprisingly, this question is
equivalent to the famous open question mentioned in the Introduction.
This is because CAP(D) belongs to a special complexity class: NP-complete. To define the notion of NP-completeness, we need the definitions of NP-hardness and polynomial-time reductions. Given two
decision problems, P1 and P2, we say that P1 is polynomial-time reducible to P2 if there is a polynomial-time algorithm T such that, for every input iP1 of P1, T modifies iP1 to an input iP2 of P2 in such a way that iP1 P NP P1 if and only if iP2 P NP P2. A
problem P1 is said to be NP-hard
if every
problem P2 in P is polynomial-time
reducible to P1. Lastly,
a problem P is said to be NP-complete
if P is in NP and
P is NP-hard (recall
that P is a specific problem, whereas P is the complexity
class of decisions problems). I shall only mention that there
are numerous practical problems, such as the channel assignment
problem and the exam timetabling problem, living in the NP-complete class. It is nontrivial
to show that a problem is NP-hard. See
Sipser (1997) for some proofs of NP-hardness.
It
is not hard to check that P
= NP if and only if there is an NP-complete
problem P which is in |