Questions in black, solutions in blue.
for (int i=0; i < n; i++)
{// locate and output the i'th biggest element
for (int j=0; j < n; j++)
if j'th input element is smaller than exactly
i other input elements, output it and break the outer loop}}
This uses logspace because the only read/write memory is the variables i and j, in O(log n) bits each, and the memory needed to compare two input items. The latter is also mostly a number from 0 to n, to point to the characters we are currently comparing. The time is about O(n4), because inside the two loops I have explicitly above, there is a loop through each of the inputs to count how many are smaller than the j'th input, and each comparison takes O(n) time in the worst case.
There is a standard "repeated squaring" algorithm to do this:
public int power (int a, int b, int m) {
if (b == 0) return 1;
if (b == 1) return a;
int halfpower = power(a,b/2,m);
if (b%2 == 0)
return (halfpower * halfpower) % m;
else
return (halfpower * halfpower * a) %m;}
As written this recursive algorithm will use O(1) variables of O(log n) bits each every time it is called. Since the stack depth could be as much as O(log n), the number of times b must be halved to reach 1, the algorithm uses O(log2) space as written.
But rewriting this iteratively we can take advantage of the fact that a and m never change, so we don't have to remember them separately in each call. Then the different values of b also fit into a single variable that is overwritten as needed, and total space usage is O(log n) bits.
The idea is simply "(∃H)(H is a Hamilton circuit)". More explicitly:
(∃H2∃f2)(∀ u ∀ v) (H(u,v) implies E(u,v)) and [(∃ w)(∀ v)(H(u,v) iff (v=w))] and (f is a numbering of the vertices such that H(f(i),f(i+1)) is always true).
H is thus a subset of the edges that forms a subgraph of out-degree one, and f assures that it is a connected subgraph. How do we say "f is a numbering of the vertices, etc.? This is easy to do in first-order, we say f is a 1-1 onto function.)
We define both HAM-CIRCUIT and HAM-PATH in terms of directed graphs, and in particular HAM-PATH = {G: G has a Hamiltonian path} which means {G: ∃ s,t: G has a Hamilton path from s to t.
To reduce HAM-CIRCUIT to HAM-PATH: I must change G to H so that H has a path iff G has a circuit. Making G=H doesn't work, because G might have a path but not a circuit. But we can take any one vertex of G and split it. Vertex w of G turns into vertices u and v of H. There is an edge from x to u in H iff there is an edge from x to w in G, and there is an edge from v to x in H iff there is an edge from w to x in G. So u keeps all the in-edges of w, and v keeps all the out edges.
Now there is a Hamilton path from v to u in H iff there is a path from w to w in G that visits all other vertices, which is true iff G has a Hamilton circuit. And the only way H can have a Hamilton path is if it goes from v to u, since there is no way into v or out of u.
To reduce HAM-PATH to HAM-CIRCUIT: Now we must change G into H so that H has a circuit iff G has a path. Let H be G together with one new vertex x, and make edges from y to x and x to y for every other vertex y. Now any circuit in H must enter y from some vertex u and leave it for some vertex v. It thus includes a path from v to u that visits all the vertices of G, and this is a Hamilton path in G. If there is a Hamilton path from some v to some u in G, conversely, we have a Hamilton circuit in H that goes from y to v to u to y.
[P] proves this as Proposition 16.3, for example, by reducing acyclic REACHABILITY to 2-SAT.
Consider testing w for membership in L(N) by applying middle-first
search to this path problem. Explain how this can be carried out in
O(log n) space rather than the O(log2 n) space required by
middle-first search on an arbitrary graph of n vertices.
The middle-first search algorithm guesses a midpoint of the alleged
path and recursively checks for the existence of a half-length path from
the start to the midpoint and the midpoint to the finish. The difference
in this case is that there are only k possible midpoints, since the midpoint
of any path from (q,i) to (r,i+2j) must be (s,i+j) for some state s.
The easiest way to show that middle-first search on this graph takes
O(log n) space is to use the alterating Turing machine version of the algorithm
and note that it runs in alternating time O(log n), which is contained in
deterministic space O(log n). (In fact, from this you get the stronger result
that this problem is in NC1.)
But let's look at an ordinary deterministic algorithm. We need a recursive
algorithm for path((q,i),(r,j)) that doesn't put all of i and j onto the
stack when it recurses. (If we put O(log n) bits on the stack every time we
recursed we'd need O(log2) total space.) So what we want is to
compute path((q,i),(r,j)) keeping i and j as global variables and only putting
q and r on the stack each time.
***I just thought of this version -- it might be interesting to write
up actual java code for it***
Last modified 4 December 2003