=========================================================================== CSC 363H Lecture Summary for Week 8 Spring 2007 =========================================================================== ----------- The class P ----------- Concentrate on "coarse scale": ignore polynomial differences. Not that they are unimportant, but that larger differences must be addressed first. "Tractability thesis": all reasonable deterministic models are polynomially equivalent (i.e., can simulate one another with at most a polynomial factor loss of efficiency). P = U_k TIME(n^k), i.e., language L belongs to P iff L can be decided by some deterministic algorithm (TM or otherwise) in worst-case polytime. Importance: - Robust: does not depend on details of model, as long as it is deterministic. - Useful: captures rough notion of efficiency. Conventions: - Describe computation in stages, where each stage is "obviously" computable in polytime (with justification) and at most polynomially many stages are executed, on any input. - Encodings: "reasonable" now means "can be encoded/decoded in polytime". In particular, numbers must be represented in some base >= 2 (unary notation requires k symbols to represent integer k, exponentially worse than the log_b(k) symbols required in base b). This is a very important point! For example, if we consider the algorithm of adding up numbers (yes, the one we learn in elementary school), this is an algorithm that allows us to solve the addition of, say, two 30-digit numbers in a time which is proportional to 30. We will not be satisified with an algorithm that works in time proportional to 2^30 which is the size of the numbers. This is why the naive algorithm that solves PRIME is not polynomial. Examples to problems in P: In general, almost all algorithms you've seen in other courses. Here are two concrete examples. - PATH = { | directed graph G has a path from s to t } - RELPRIME = { | x and y are relatively prime integers } ------------ The class NP ------------ We've seen that nondeterminism is not "practical". Why use it, then? Because a large number of real-life problems have no known efficient solution (i.e., are not known to belong to P), yet can be solved efficiently using nondeterminism. So nondeterminism allows us to characterize a large class of problems. Also, nondeterminism is an elegant way to add (what seems to be) significant power to the model. Nondeterminism vs. Verifiers: - Acceptance condition for NTMs: there exists an accepting computation path. - Verifiers capture that notion directly: verifier for language L = algorithm V that takes input such that x in L iff V accepts for some c. Runtime measured as a function of n=|x| only (ignoring c). NTIME(t(n)) = { L : L is a language decided by a NTM in worst-case time O(t(n)) } NP = U_k NTIME(n^k) = { L : L is decided by some polytime NTM } = { L : L has a polytime verifier } By tractability thesis, NP independent of specific details of nondeterministic model (as long as it's nondeterministic). Examples: - COMPOSITES = { x : x is a composite number } COMPOSITES in P? Unknown (checking all possible factors not polytime.) [ Actually, it is in P since Primes was recently shown to be in P, but this goes beyond the scope of this course.] COMPOSITES in NP? Yes: On input : 1. Check that 1 < c < x. 2. Check that c divides x evenly. Accept if all checks succeed; reject if any fail. If x is composite, then the verifier will accept when c is a factor of x. If x is prime, then the verifier will reject for all values of c. Moreover, the verifier runs in polytime: comparing integers (1 < c < x) can be done in linear time; dividing integers can be done in quadratic time. - HAMPATH = { : G is an undirected graph that contains a Hamiltonian path, i.e., a path that includes every vertex exactly once } HAMPATH in P? Unknown (checking all possible paths not polytime.) HAMPATH in NP? Yes: On input : 1. Check c encodes a list of vertices. 2. Check c contains every vertex of G exactly once. 3. Check G contains every edge between successive vertices in c. Accept if all checks succeed; reject if any fail. By definition of the language, if G in HAMPATH, then verifier accepts for some value of c (a Hamiltonian path in G); if G not in HAMPATH, then verifier rejects for all values of c. Moreover, verifier runs in polytime: if G contains n vertices and m edges, runtime is at most O(n^2 m).