From Lecture: Hard problems and undecidability
Computable Problem
Time Complexity
Tractable Problems (P)
Polynomial algorithms: $O(n^k)$ for some k ($k\leq4$)
Exponential Time (EXPTIME)
Exponential Time algorithm: $O((2^k)^n)$
Nondeterministic Polynomial Problems (NP)
e.g.: Hamilton cycles, SAT (Boolean satisfiability), Graph coloring
How would you solve a very hard problem? If you have enough resources (space & time), you can just randomly generate some results and check the result’s correctness.
Therefore, the key is that you have to check the correctness in deterministic time (polynomial time). In fact, all of them can be solved by exponential-time algorithms that essentially try all exponentially many possible solutions, but this approach is infeasible
NP-Complete: All NP problems can be expressed using the three NP examples above. These three problems are said to be NP-Complete. In fact, SAT is the most frequently used one. There are many people working on how to solve well-formed SAT problems in a reasonable amount of time. e.g. You can transform a Hamilton cycle problem into an SAT problem and use those SAT algorithms to solve it
Space Complexity
- L: logarithmic space
- PSPACE: polynomial space
- NPSPACE: nondeterministic polynomial space
Inclusion Relationships
$L\sube P\sube NP \sube PSPACE = NPSPACE \sube EXPTIME$
Unknow Problems
- $O(Factoring)\in P$ ?
- NP = EXPTIME ?
- P = NP ?
- L = P ?
because L≠PSPACE, we know that at least one of the inequalities L≠P, P≠NP, and NP≠PSPACE must hold, but we don’t know which.
Incomputable Problems (Halting Problem)
interpret
claim:
- we can convert a program to an AST
- we can build an interpreter for the AST
We have a simple program p that returns only a boolean value.
If we can’t determine whether such simple programs terminate, then of course we have no hope of determining whether more complex programs do.
1 | /** @return result of p on input i |
method interpret
is correct $\iff$ $\forall(p,i)$ p.main(i)==interpret(p,i)
terminate
1 | /** Return whether p would halt on i */ |
Autological & Heterological
A program is either autological or heterological:
autological: program is autological if it returns true when provided its own AST (return value for other inputs not defined)
heterological: either returns false on itself as input or doesn’t terminate
Self-Reference
You cannot build both interpret
and terminates
for a program.
1 | class H{ |
can’t directly return !interpret(p,p)
because we are not sure whether P will terminate. Therefore, we have to first check whether it terminates or not.
Now we want to pass H itself to H.main
note: H can always terminate no matter what p is (because if p terminates, it calls interpret, which always returns a value if p terminates; if p doesn’t terminate, it just returns a boolean value of true) Therefore, if we pass H itself, it goes into the if (terminates) {}
block, which returns ! interpret(h,h) = !h.main(h)
Therefore, calling h.main(h)
actually returns !h.main(h)
But how can h returns the negation of what it’s supposed to return? Therefore, there doesn’t exist a program like h. \
Therefore, we cannot build both interpret
and terminate
. Since we can and did build an interpreter, terminate
is what we can’t really build. That’s the why this problem is called a “halting problem”.
Implications
Theoretical Implications
Every language is either:
- too expressive to analyze termination precisely (incomplete)
- not expressive to build on interpreter (inconsistent)
- makes a program like H illegal (impossible to be both comprehensive and consistent)
practical implications
e.g. Can we write a IntelliJ plugin to check NullPointerException
? if NPE is decidable, consider the codes below
1 | h; |
Reduction: if algorithm for problem A can be used to solve B (with suitable efficiency), A must be at least as hard as B. (In the example of above A: NPE checker B: halting problem)