How it works
The explanation, with examples you can run.
P and NP
P is the class of decision problems (yes/no questions) that can be solved in polynomial time: O(n), O(n log n), O(n³) and so on. Sorting, shortest paths, matching and primality testing are in P. Polynomial algorithms aren't always fast in practice, but they scale in a way exponential ones don't.
NP is the class of problems where a "yes" answer can be verified in polynomial time, given a certificate. Is there a route visiting every city with total length under 1,000 km? If someone hands you a route, you can check it quickly. Finding one may require trying a huge number of possibilities. Every problem in P is also in NP.
Whether P = NP, whether every quickly checkable problem is also quickly solvable, is the most famous open problem in computer science, one of the Clay Institute's Millennium Prize Problems. Most researchers believe P ≠ NP, and a lot of practical cryptography relies on certain problems being hard, although the problems it uses aren't known to be NP-complete.
NP-complete and NP-hard
In 1971 Stephen Cook showed that Boolean satisfiability (SAT, "can these logical constraints all be true at once?") is NP-complete: it is in NP, and every NP problem can be translated into it in polynomial time. Thousands of problems have since been shown to be equivalent: graph colouring, the decision version of the travelling salesman problem, bin packing, scheduling with constraints, Sudoku on n² × n² grids. A polynomial algorithm for any one of them would give one for all. NP-hard problems are at least that hard but needn't be in NP themselves (optimisation versions often are NP-hard).
What to do when your problem is NP-hard:
- Small inputs: exact search, backtracking or dynamic programming is fine.
- Real instances: SAT, SMT and integer programming solvers are remarkably good on structured, real-world inputs, even with millions of variables.
- Approximations and heuristics: greedy algorithms, local search and simulated annealing give good answers fast, some with proven bounds (for example within a factor of 2 of optimal).
- Change the problem: add constraints, accept "good enough", or precompute.
Undecidable problems
Some problems have no algorithm at all. In 1936 Alan Turing proved that the halting problem is undecidable: no program can take any program and input and always correctly say whether it eventually stops. The intuition is a contradiction. Suppose halts(p, x) existed. Write a program that asks halts(self, self) and does the opposite: loops forever if the answer is "halts", stops if the answer is "loops". Neither answer can be right, so halts can't exist.
Rice's theorem generalises this: every non-trivial question about what a program computes (does it ever return null? is it equivalent to this other program? does it leak this secret?) is undecidable in general. That is why static analysers, type checkers and security scanners are all conservative approximations. They either reject some correct programs, miss some bugs, or answer "unknown", and choosing which trade-off a tool makes is part of designing it.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.