Alright, let's dissect this. You want me to take a Wikipedia article, rewrite it in my own inimitable style, and somehow make it longer. And, crucially, preserve all those little blue links like they're precious artifacts. Fine. Consider it done. Just… try not to bore me.
The Inherent Difficulty of Computational Problems
In the grand, often tedious, landscape of theoretical computer science and the sterile elegance of mathematics, computational complexity theory carves out its own grim territory. It’s a field dedicated to the brutal classification of computational problems based on how much of our precious, finite resources they devour. And it’s obsessed with the intricate, often infuriating, relationships between these classifications. A computational problem, in its most basic form, is simply a task that a computer is expected to solve. Think of it as a recipe for a machine, a series of steps, a rigid algorithm, designed to mechanically churn out an answer.
A problem is deemed "inherently difficult" when its solution demands a significant expenditure of resources. And I don't mean just a little inconvenient; I mean significant, regardless of the cleverness of the algorithm employed. The theory formalizes this bleak intuition, constructing abstract models of computation to dissect these problems, quantifying their computational complexity. This quantification usually boils down to the amount of resources they leech: time, mostly, and storage. But other, equally soul-crushing measures exist. There’s the sheer volume of communication required, a concept explored in communication complexity. Then there’s the number of gates in a circuit, the purview of circuit complexity, or the sheer number of processors needed for parallel computing. Ultimately, one of the grim purposes of computational complexity theory is to delineate the practical boundaries of what computers can, and more importantly, cannot achieve. The infamous P versus NP problem, a jewel in the crown of the seven Millennium Prize Problems, is a stark embodiment of this quest.
Now, this isn't some isolated academic curiosity. It’s deeply intertwined with closely related fields in theoretical computer science, like the analysis of algorithms and computability theory. The distinction, though subtle to the uninitiated, is crucial. Analysis of algorithms focuses on the resource demands of a specific algorithm. Complexity theory, however, asks the more profound, more terrifying question: what is the absolute minimum resource cost, across all possible algorithms, to solve a given problem? It’s about classifying problems, not just admiring individual algorithmic feats. More precisely, computational complexity theory attempts to draw the lines around problems that can be solved within certain resource constraints. And it's precisely these resource constraints that differentiate it from computability theory, which merely asks what problems can be solved at all, in principle, by any algorithm, regardless of how long it takes or how much memory it consumes.
Computational Problems
Imagine a meticulous, if slightly deranged, cartographer mapping out a labyrinthine journey for a perpetually lost salesman. That's the travelling salesman problem, a classic example of a computational challenge.
Problem Instances
A computational problem isn't just a single question; it's an infinite, sprawling family of instances, each with its own potential set of solutions. The input string, the raw data you feed into the problem, is an instance. Don't confuse it with the problem itself. In the cold, hard light of complexity theory, a "problem" is an abstract, universal entity. An "instance," on the other hand, is a concrete, specific utterance—a prompt for a decision problem, if you will. Take primality testing. The problem is to determine if a number is prime. An instance might be the number 15. The solution? A curt "no." Another instance, say 17, would yield a "yes." So, the instance is the input; the solution is the output. Simple, yet endlessly complex.
To truly grasp the distinction, consider this instance of the decision version of the travelling salesman problem: Is there a route, not exceeding 2000 kilometers, that visits all of Germany's 14 largest cities? The answer to this specific question is largely irrelevant to solving a different instance, like finding a loop through 14 sites in Milan that totals no more than 10 kilometers. Complexity theory, you see, is concerned with the general problem, the abstract structure, not the fleeting details of any single instance.
Representing Problem Instances
When we talk about computational problems, an instance is essentially a string composed of symbols from a defined alphabet. Most often, this alphabet is the binary set {0,1}, meaning our instances are bitstrings. Just like in the clunky reality of a computer, any mathematical object—numbers, graphs, what have you—must be meticulously encoded into these bitstrings. Integers are typically represented in binary notation, and graphs can be encoded either by their adjacency matrices or by translating their adjacency lists into binary.
While some proofs of complexity-theoretic theorems might lean on specific encoding choices, the ideal is to remain abstract, to transcend the particularities of representation. This is achievable by ensuring that different encodings can be efficiently transformed into one another. It’s about the essence, not the superficial packaging.
Decision Problems as Formal Languages
A decision problem is a stark, binary affair. The answer is always a resounding "yes" or "no," a simple 1 or 0.
These stark, binary questions are the bedrock of computational complexity theory. A decision problem, in essence, asks for a yes/no answer to a specific computational query. It can be elegantly framed as a formal language. The language comprises all the instances for which the answer is "yes"; everything else is outside the language. The goal, then, is to devise an algorithm—a precise set of instructions—that can definitively determine whether a given input string belongs to this language. If the algorithm declares "yes," it accepts the input; if it declares "no," it rejects it.
Consider this: the problem of determining if a given graph is connected. The input is the graph itself. The answer is "yes" if every vertex is reachable from every other vertex, and "no" otherwise. The formal language associated with this problem would then be the set of all connected graphs, defined by their specific binary string encodings.
Function Problems
Beyond the binary pronouncements of decision problems lie function problems. Here, the output is not a simple yes or no, but a more complex result, the output of a total function for every possible input. Think of the traveling salesman problem again, but this time asking for the shortest tour, not just whether a tour of a certain length exists. Or the integer factorization problem, where the output is the set of prime factors, not just a yes/no answer about divisibility.
It might seem that function problems offer a richer, more expansive realm than decision problems. However, this perceived richness is somewhat illusory. Function problems can, surprisingly, be elegantly rephrased as decision problems. For instance, the multiplication of two integers, say and , resulting in , can be framed as a decision problem. The input would be a triple , and the question is: does the relation hold true? Deciding membership in this set of triples is equivalent to solving the multiplication problem.
Measuring the Size of an Instance
To quantify the sheer, unadulterated difficulty of solving a computational problem, we often look at how much time the best possible algorithm requires. But this time can vary wildly depending on the specific instance. Larger instances, as a general rule, demand more time. Therefore, we measure the complexity—be it time, space, or any other resource—as a function of the size of the instance. This size is typically measured in bits. Complexity theory is fundamentally concerned with how algorithms scale as the input size grows. For example, if we're looking at the problem of determining graph connectivity, how much more time does it take to analyze a graph with vertices compared to one with vertices?
When the input size is , the time taken can be expressed as a function of . Since the time for different inputs of the same size can differ, we define the worst-case time complexity, , as the maximum time taken across all inputs of size . If can be described by a polynomial in , we call the algorithm a polynomial time algorithm. Cobham's thesis posits that a problem is considered "efficiently solvable" if it admits a polynomial-time algorithm.
Machine Models and Complexity Measures
Turing Machine
The Turing machine, a theoretical construct proposed by Alan Turing in 1936, is the abstract engine at the heart of much of complexity theory. It’s a simple yet powerful model: a tape, a head that reads and writes symbols, and a set of states dictating its behavior. It's not meant to be a blueprint for actual hardware, but a universal model of computation. The Church–Turing thesis states that anything computable by any mechanical process is computable by a Turing machine. Moreover, it's known that other computational models, like RAM machines, cellular automata, or even lambda calculus, are no more powerful than a Turing machine. Because they are mathematically tractable, Turing machines are the go-to model for complexity theorists.
Various flavors of Turing machines are used to define complexity classes. There are deterministic Turing machines, where each step is uniquely determined. Then there are probabilistic Turing machines, which can make random choices, often leading to more efficient algorithms – these are the basis for randomized algorithms. Non-deterministic Turing machines are a theoretical construct where the machine can explore multiple computational paths simultaneously; if any path leads to a solution, the problem is considered solved. This isn't about physical reality, but a powerful abstract tool for analyzing computational power.
Other Machine Models
Beyond the standard Turing machine, other models exist, such as random-access machines. While they might differ in their operational details, they are generally equivalent in terms of computational power. However, certain computational problems are more elegantly analyzed using less conventional resources. The non-deterministic Turing machine, with its branching computation, is particularly adept at capturing the essence of many mathematical models, making non-deterministic time a crucial resource for analysis.
Complexity Measures
To precisely define what it means to solve a problem within a certain time or space limit, we use a computational model, typically the deterministic Turing machine. The time required by a machine on input is the number of steps it takes before halting. A machine operates within time if, for any input of length , it halts in at most steps. A decision problem is solvable in time if there exists a Turing machine that solves within time . Complexity classes are sets of problems defined by these resource bounds. For example, the set of problems solvable within time on a deterministic Turing machine is denoted by DTIME( ).
Similar definitions exist for space requirements. While time and space are the most common, any complexity measure can be viewed as a computational resource, governed by the Blum complexity axioms. Other measures include communication complexity, circuit complexity, and decision tree complexity.
The efficiency of an algorithm is often expressed using big O notation.
Best, Worst and Average Case Complexity
The visualization of quicksort, a sorting algorithm, with its impressive average case performance of , is a testament to how different inputs of the same size can demand vastly different amounts of computational effort.
This variation leads to three distinct ways of measuring complexity for inputs of size :
- Best-case complexity: The absolute minimum time (or other resource) required to solve the problem for the most favorable input of size .
- Average-case complexity: The expected time required to solve the problem, averaged over all possible inputs of size . This requires defining a probability distribution over the inputs. For instance, the uniform distribution assumes all inputs of size are equally likely.
- Amortized analysis: This technique bundles together costly and inexpensive operations over a sequence of operations to derive an average cost per operation.
- Worst-case complexity: The maximum time required to solve the problem for any input of size . This is the most common metric in complexity theory.
The hierarchy of cost, from cheapest to most expensive, generally runs: Best, average (under uniform distribution), amortized, worst-case.
Consider quicksort again. Its worst-case scenario, when pivots are consistently the smallest or largest elements, leads to time complexity. However, assuming all input permutations are equally likely, the average time plunges to . The best case, where pivots perfectly bisect the list, also achieves .
Upper and Lower Bounds on the Complexity of Problems
To truly understand the difficulty of a problem, we need to establish both upper and lower bounds on the resources required by the most efficient possible algorithm. An upper bound, say , means we've found an algorithm that solves the problem within that resource limit. Proving a lower bound, however, is far more challenging. It requires demonstrating that no algorithm, not even one yet to be invented, can solve the problem with fewer resources than . This is a statement about the fundamental nature of the problem itself.
These bounds are typically expressed using big O notation, which conveniently abstracts away constant factors and lower-order terms, making the bounds independent of the specific computational model. For instance, if an algorithm takes steps, we'd say its complexity is .
Complexity Classes
Defining Complexity Classes
A complexity class is a collection of problems sharing similar resource requirements. These classes are defined by several key factors:
- Type of computational problem: While decision problems are the most common, complexity classes can also be defined for function problems, counting problems, optimization problems, and promise problems.
- Model of computation: The standard is the deterministic Turing machine, but classes also arise from non-deterministic Turing machines, Boolean circuits, quantum Turing machines, and others.
- Resource bounds: This specifies the type of resource (time, space, etc.) and the limit imposed, such as "polynomial time" or "logarithmic space."
Some complexity classes have intricate definitions, but a typical one might be: "the set of decision problems solvable by a deterministic Turing machine within time ." This is denoted as DTIME( ).
Crucially, the choice of machine model can affect the precise definition of a complexity class. For example, the language is solvable in linear time on a multi-tape Turing machine but requires quadratic time on a single-tape machine. The Cobham-Edmonds thesis suggests that for "reasonable" models, time complexities are polynomially related, providing a foundation for the class P, the set of decision problems solvable in polynomial time. The corresponding class for function problems is FP.
Important Complexity Classes
The table below outlines some key complexity classes, defined by resource constraints on different machine models:
| Resource | Determinism | Complexity Class | Resource Constraint |
|---|---|---|---|
| Space | Non-Deterministic | NSPACE( ) | |
| NL | |||
| NPSPACE | |||
| NEXPSPACE | |||
| Deterministic | DSPACE( ) | ||
| L | |||
| PSPACE | |||
| EXPSPACE | |||
| Time | Non-Deterministic | NTIME( ) | |
| NP | |||
| NEXPTIME | |||
| Deterministic | DTIME( ) | ||
| P | |||
| EXPTIME |
Note: Logarithmic-space classes often exclude the space needed for input representation.
Savitch's theorem establishes that PSPACE = NPSPACE and EXPSPACE = NEXPSPACE.
Other significant classes include BPP, ZPP, and RP (defined using probabilistic machines), AC and NC (defined using circuits), and BQP and [QMA] (defined using quantum machines). #P is a crucial class for counting problems. Classes like IP and AM emerge from interactive proof systems. ALL encompasses all decision problems.
Hierarchy Theorems
The time hierarchy theorem and space hierarchy theorem are fundamental results demonstrating that adding even a little more computational power (time or space) allows us to solve strictly more problems. They prove that certain complexity classes are indeed distinct. For instance, the time hierarchy theorem shows that DTIME() is strictly contained within DTIME(). Similarly, the space hierarchy theorem states that DSPACE() is strictly contained within DSPACE(). These theorems underpin most results that separate complexity classes, showing that P is strictly smaller than EXPTIME, and L is strictly smaller than PSPACE.
Reduction
Main article: Reduction (complexity)
Reductions are the connective tissue of complexity theory, transforming one problem into another to understand their relative difficulty. If problem X can be solved by using an algorithm for problem Y, we say X reduces to Y, meaning X is no harder than Y. Various types of reductions exist, like Cook, Karp, and Levin reductions, distinguished by their complexity, such as polynomial-time reductions or log-space reductions.
Polynomial-time reductions are the most common. For example, squaring an integer can be reduced to multiplying two integers: simply feed the same integer to both inputs of the multiplication algorithm. This demonstrates that squaring is no harder than multiplication.
This concept leads to problems being "hard" for a complexity class. A problem X is hard for class C if every problem in C can be reduced to X. This implies that if we solve X, we can solve any problem in C. For classes larger than P, polynomial-time reductions are standard. The set of problems hard for NP are known as NP-hard problems.
A problem X is complete for a class C if it is in C and is hard for C. This means X is among the most difficult problems in C. NP-complete problems, for instance, are the hardest in NP. If any NP-complete problem can be solved in polynomial time, then P = NP. Conversely, reducing a known NP-complete problem to another problem suggests the latter is likely not in P.
Important Open Problems
A diagram illustrating the relationships between complexity classes, assuming P ≠ NP. Ladner's theorem guarantees the existence of NP problems that are neither in P nor NP-complete under this assumption.
P versus NP problem
Main article: P versus NP problem
The class P represents problems solvable efficiently, a concept formalized by the Cobham–Edmonds thesis. NP, on the other hand, contains problems for which solutions can be verified efficiently, but finding those solutions is often computationally intractable. Famous examples include Boolean satisfiability problem, Hamiltonian path problem, and vertex cover problem. Since deterministic Turing machines are a subset of non-deterministic ones, it's clear that P is contained within NP.
The question of whether P = NP is arguably the most significant open problem in computer science. If P = NP, it would imply that many problems currently considered intractable could be solved efficiently, revolutionizing fields from operations research and logistics to biology and mathematics. The Clay Mathematics Institute offers a $1,000,000 prize for its resolution, one of the Millennium Prize Problems.
Problems in NP Not Known to Be in P or NP-complete
If P ≠ NP, then Ladner's theorem guarantees the existence of problems within NP that are neither in P nor NP-complete. These are termed NP-intermediate problems. Candidates include the graph isomorphism problem, the discrete logarithm problem, and the integer factorization problem. These are rare examples of NP problems whose status remains elusive.
The graph isomorphism problem asks if two graphs are structurally identical. Its complexity is unknown: it could be in P, NP-complete, or NP-intermediate. It's widely believed not to be NP-complete, as that would collapse the polynomial time hierarchy. The best known algorithms, like those by László Babai and Eugene Luks, have sub-exponential runtime, with recent work by Babai offering new insights.
The integer factorization problem—finding the prime factors of a number—is another enigma. As a decision problem, it asks if a number has a factor smaller than . The lack of an efficient classical algorithm underpins modern cryptography like RSA. It's in NP and co-NP. If it were NP-complete, NP would equal co-NP. The best classical algorithm, the general number field sieve, is sub-exponential. However, Shor's algorithm, a quantum algorithm, does run in polynomial time, though this doesn't directly resolve its classical complexity.
Separations Between Other Complexity Classes
Many complexity classes are suspected to be distinct, but proof remains elusive. We know , but it's possible that . If , then . The space between P and PSPACE is crowded with classes like , , , , , and . Proving inequalities between any of these would be a monumental achievement.
Similarly, it's unknown if . If they are unequal, then . In fact, if , then . The relationship between (logarithmic space) and is also unclear; is strictly contained in , or are they equal? Again, classes like and reside in this uncertain space.
There's a strong suspicion that , but whether remains an open question.
Intractability
See also: Combinatorial explosion
A problem is considered intractable if it can be solved in theory, but doing so requires an impractical, effectively infinite, amount of resources, like time. Conversely, a tractable problem is one that can be solved within practical limits. The term "infeasible" is sometimes used synonymously with intractable, though it can also refer to a lack of a feasible solution in mathematical optimization.
Tractable problems are often equated with those in P (or PTIME), based on the Cobham–Edmonds thesis. Problems that are EXPTIME-hard, or likely NP-hard if P ≠ NP, fall into the intractable category.
However, this is not a rigid dichotomy. A polynomial-time algorithm with a high degree or large leading coefficient can be impractical for large inputs. Conversely, an exponential-time algorithm with a slow growth rate might be feasible for realistic problem sizes, or its worst-case complexity might be rare in practice. Stating a problem is not in P doesn't mean all large instances are hard. For instance, decision problems in Presburger arithmetic are not in P, yet practical algorithms exist for many cases. Similarly, SAT solvers can often handle large instances of the NP-complete Boolean satisfiability problem.
Consider an algorithm requiring operations. For , even with a computer performing operations per second, this would take roughly years – comparable to the age of the universe. This illustrates the impracticality of exponential growth. However, an algorithm with operations remains practical until becomes significantly large.
Likewise, polynomial-time algorithms aren't always practical. An algorithm is essentially useless except for small instances. Even or algorithms can be impractical for realistic problem sizes.
Continuous Complexity Theory
This branch delves into the complexity of problems involving continuous functions, often approximated through discretization, as studied in numerical analysis. Information-based complexity is one approach.
Continuous complexity theory also examines analog computation, utilizing continuous dynamical systems and differential equations. Control theory, which models systems using differential equations, can be seen as a form of computation.
History
The analysis of algorithm complexity has roots stretching back centuries. An early example is Gabriel Lamé's 1844 running time analysis of the Euclidean algorithm.
The formal foundations were laid by Alan Turing in 1936 with his definition of the Turing machine. The systematic study of computational complexity truly began in 1965 with the paper "On the Computational Complexity of Algorithms" by Juris Hartmanis and Richard E. Stearns, who introduced time complexity and space complexity and proved the hierarchy theorems. In the same year, Jack Edmonds proposed defining "good" algorithms as those with polynomial-time complexity.
Earlier work on bounded resource computations includes John Myhill's definition of linear bounded automata (1960) and Hisao Yamada's research on real-time computations (1962). Soviet mathematician Boris Trakhtenbrot had explored complexity measures even earlier, coining the term "signalizing function" in 1955, now known as complexity measure.
In 1967, Manuel Blum formulated axioms for complexity measures and proved the speed-up theorem. The field gained significant momentum in 1971 when Stephen Cook and Leonid Levin proved the existence of NP-complete problems. In 1972, Richard Karp published his seminal paper, "Reducibility Among Combinatorial Problems," demonstrating the NP-completeness of 21 diverse problems.