]> mj.ucw.cz Git - saga.git/commitdiff
Fixes to bucket sorting section.
authorMartin Mares <mj@ucw.cz>
Mon, 7 Apr 2008 14:07:48 +0000 (16:07 +0200)
committerMartin Mares <mj@ucw.cz>
Mon, 7 Apr 2008 14:07:48 +0000 (16:07 +0200)
ram.tex

diff --git a/ram.tex b/ram.tex
index 11876213b79cb3d3cbf1f7d787fcbced4d941ec4..af55492abec14903bc412626abf0502e02065959 100644 (file)
--- a/ram.tex
+++ b/ram.tex
@@ -263,15 +263,14 @@ data structures in the Okasaki's monograph~\cite{okasaki:funcds}.
 
 \section{Bucket sorting and unification}\id{bucketsort}%
 
-The Contractive Bor\o{u}vka's algorithm (\ref{contbor}) needed to contract a~given
-set of edges in the current graph and flatten it afterwards, all this in time $\O(m)$.
+The Contractive Bor\o{u}vka's algorithm (\ref{contbor}) needs to contract a~given
+set of edges in the current graph and then flatten the graph, all this in time $\O(m)$.
 We have spared the technical details for this section, in which we are going to
-explain several techniques based on bucket sorting. These will be useful in further
-algorithms, too.
+explain several rather general techniques based on bucket sorting.
 
-As already suggested in the proof of Lemma \ref{contbor}, contractions can be performed
-in linear time by building an~auxiliary graph and finding its connected components.
-We will thus take care only of the subsequent flattening.
+As we have already suggested in the proof of Lemma \ref{contbor}, contractions
+can be performed in linear time by building an~auxiliary graph and finding its
+connected components. We will thus take care only of the subsequent flattening.
 
 \paran{Flattening on RAM}%
 On the RAM, we can view the edges as ordered pairs of vertex identifiers with the
@@ -289,7 +288,7 @@ same size. We could then waste a~super-linear amount of time by scanning the inc
 sparse arrays, most of the time skipping unused entries.
 
 To avoid this problem, we have to renumber the vertices after each contraction to component
-identifiers from the auxiliary graph and create a~new vertex array. This helps to
+identifiers from the auxiliary graph and create a~new vertex array. This helps
 keep the size of the representation of the graph linear with respect to its current
 size.
 
@@ -303,26 +302,26 @@ without indexing of arrays.
 We will keep a~list of the per-vertex structures that defines the order of~vertices.
 Each such structure will be endowed with a~pointer to the head of the list of items in
 the corresponding bucket. Inserting an~edge to a~bucket can be then done in constant time
-and scanning all~$n$ buckets takes $\O(n+m)$ time.
+and scanning the contents of all~$n$ buckets takes $\O(n+m)$ time.
 
 \paran{Tree isomorphism}%
-Another nice example of pointer-based radix sorting is a~pointer algorithm for
+Another nice example of pointer-based radix sorting is a~Pointer machine algorithm for
 deciding whether two rooted trees are isomorphic. Let us assume for a~moment that
-the outdegree of each vertex is at most a~fixed constant~$k$. We sort the subtrees
-of both trees by their depth by running the depth-first search to calculate the
-depths and bucket-sorting them with $n$~buckets afterwards.
+the outdegree of each vertex is at most a~fixed constant~$k$. We begin by sorting the subtrees
+of both trees by their depth. This can be accomplished by running depth-first search to calculate
+the depths and bucket-sorting them with $n$~buckets afterwards.
 
-Then we proceed from depth~1 to the maximum depth and for each of them we identify
-the isomorphism equivalence classes of subtrees of that particular depth. We will assign some
-sort of identifiers to the classes; at most~$n+1$ of them are needed as there are
+Then we proceed from depth~0 to the maximum depth and for each of them we identify
+the isomorphism equivalence classes of subtrees of that particular depth. We will assign
+unique identifiers all such classes; at most~$n+1$ of them are needed as there are
 $n+1$~subtrees in the tree (including the empty subtree). As the PM does not
-have numbers as a~first-class type, we just create a~``\df{yardstick}'' ---
-a~list of $n+1$~distinct items and we use pointers to these items as identifiers.
-Isomorphism of the whole trees can be finally decided by comparing the
+have numbers as a~first-class type, we create a~``\df{yardstick}'' ---a~list
+of $n+1$~distinct items--- and we use pointers to these ``ticks'' as identifiers.
+When we are done, isomorphism of the whole trees can be decided by comparing the
 identifiers assigned to their roots.
 
-Suppose that classes of depths $1,\ldots,d-1$ are already computed and we want
-to identify those of depth~$d$. We will denote their number of~$n_d$. We take
+Suppose that classes of depths $0,\ldots,d-1$ are already computed and we want
+to identify those of depth~$d$. We will denote their count of~$n_d$. We take
 a~root of every such tree and label it with an~ordered $k$-tuple of identifiers
 of its subtrees; when it has less than $k$ sons, we pad the tuple with empty
 subtrees. Tuples corresponding to isomorphic subtrees are identical up to
@@ -341,14 +340,15 @@ while we have only $n_d$~items to sort, we have to scan all $n$~buckets. This ca
 be easily avoided if we realize that the order of buckets does not need to be
 fixed --- in every pass, we can use a~completely different order and it still
 does bring the equivalent tuples together. Thus we can keep a~list of buckets
-which were used in the current pass and look only inside these buckets. This way,
-the pass takes $\O(n_d)$ time only and the whole algorithm is $\O(\sum_d n_d) = \O(n)$.
+which are used in the current pass and look only inside these buckets. This way,
+we reduce the time spent in a~single pass to $\O(n_d)$ and the whole algorithm
+takes $\O(\sum_d n_d) = \O(n)$.
 
 Our algorithm can be easily modified for trees with unrestricted degrees.
 We replace the fixed $d$-tuples by general sequences of identifiers. The first
 sort does not need any changes. In the second sort, we proceed from the first
-position to the last one and after each bucket sort pass we put aside the sequences
-that have just ended. (They are obviously not equivalent to any other sequences.)
+position to the last one and after each bucket-sorting pass we put aside the sequences
+that have just ended. They are obviously not equivalent to any other sequences.
 The second sort is linear in the sum of the lengths of the sequences, which is
 $n_{d+1}$ for depth~$d$. We can therefore decide isomorphism of the whole trees
 in time $\O(\sum_d (n_d + n_{d+1})) = \O(n)$.
@@ -380,53 +380,55 @@ is roughly $k$~times smaller than the original graph, so we can use a~less effic
 algorithm and it will still run in linear time with respect to the size of the original
 graph.
 
-This type of decomposition was traditionally used for trees, especially in the
+This kind of decomposition is traditionally used for trees, especially in the
 algorithms for the Lowest Common Ancestor problem (cf.~Section \ref{verifysect}
 and the survey paper \cite{alstrup:nca}) and for online maintenance of marked ancestors
 (cf.~Alstrup et al.~\cite{alstrup:marked}). Let us take a~glimpse at what happens when
-we set~$k$ to $1/4\cdot\log n$. There are at most $2^{2k} = \sqrt n$ non-isomorphic subtrees,
+we decompose a~tree with $k$ set to~$1/4\cdot\log n$. There are at most $2^{2k} = \sqrt n$ non-isomorphic subtrees of size~$k$,
 because each isomorphism class is uniquely determined by the sequence of $2k$~up/down steps
 performed by depth-first search. Suppose that we are able to decompose the input and identify
 the equivalence classes of microtrees in linear time, then solve the problem in time $\O(\poly(k))$ for
-each microtree and finally in $\O(n'\log n')$ for the macrotree. When we put these pieces
-together, we get an~algorithm with time complexity $\O(n + \sqrt{n}\cdot\poly(\log n) + n/\log n\cdot\log(n/\log n)) = \O(n)$
-for the whole problem.
+each microtree and finally in $\O(n'\log n')$ for the macrotree of size $n'=n/k$. When we put these pieces
+together, we get an~algorithm for the whole problem which achieves time complexity $\O(n
++ \sqrt{n}\cdot\poly(\log n) + n/\log n\cdot\log(n/\log n)) = \O(n)$.
 
 Decompositions are usually implemented on the RAM, because subgraphs can be easily
-encoded in numbers, which can be then used to index arrays containing precomputed
-results. As the previous algorithm for subtree isomorphism shows, indexing is not
-required for identifying and equivalent microtrees and it can be replaced by bucket
+encoded in numbers, which can be then used to index arrays containing the precomputed
+results. As the previous algorithm for subtree isomorphism shows, indexing is not strictly
+required for identifying equivalent microtrees and it can be replaced by bucket
 sorting on the Pointer machine. Buchsbaum et al.~\cite{buchsbaum:verify} have extended
-this technique to general graphs in form of topological graph computations.
+this technique to general graphs in form of so called topological graph computations.
+Let us define them.
 
 \defn
-A~\df{graph computation} takes a~\df{labeled undirected graph} as its input. The labels of both
+A~\df{graph computation} is a~function that takes a~\df{labeled undirected graph} as its input. The labels of its
 vertices and edges can be arbitrary symbols drawn from a~finite alphabet. The output
 of the computation is another labeling of the same graph. This time, the vertices and
-edges can be labeled with not only symbols of the alphabet, but also with alphabet pointers to the vertices
-and edges of the graph given as the input, and possibly also with pointers to outside objects.
-A~graph computation is called \df{topological} iff it produces isomorphic
-outputs for isomorphic inputs. The isomorphism has of course preserve not only
+edges can be labeled with not only symbols of the alphabet, but also with pointers to the vertices
+and edges of the input graph, and possibly also with pointers to outside objects.
+A~graph computation is called \df{topological} if it produces isomorphic
+outputs for isomorphic inputs. The isomorphism of course has to preserve not only
 the structure of the graph, but also the labels in the obvious way.
 
 \obs
 The topological graph computations cover a~great variety of graph problems, ranging
-from searching for spanning trees or Eulerian tours to the Traveling Salesman Problem.
-The MST problem itself however does not lie in this class, because we do not have any means
-of representing the edge weights, unless of course there is only a~fixed amount
-of possible weights.
+from searching for matchings or Eulerian tours to finding Hamilton circuits.
+The MST problem itself however does not belong to this class, because we do not have any means
+of representing the edge weights as labels, unless of course there is only a~fixed amount
+of possible values.
 
 As in the case of tree decompositions, we would like to identify the equivalent subgraphs
 and process only a~single instance from each equivalence class. The obstacle is that
 graph isomorphism is known to be computationally hard (it is one of the few
-problems that are neither known to lie in~$\rm P$ nor to be $\rm NP$-complete,
+problems that are neither known to lie in~$\rm P$ nor to be $\rm NP$-complete;
 see Arvind and Kurur \cite{arvind:isomorph} for recent results on its complexity).
 We will therefore manage with a~weaker form of equivalence, based on some sort
 of graph encodings:
 
 \defn
 A~\df{canonical encoding} of a~given labeled graph represented by adjancency lists
-can be obtained by running the depth-first search on the graph. When we enter
+is obtained by running the depth-first search on the graph and recording its traces.
+We start with an~empty encoding. When we enter
 a~vertex, we assign an~identifier to it (again using a~yardstick to represent numbers)
 and we append the label of this vertex to the encoding. Then we scan all back edges
 going from this vertex and append the identifiers of their destinations, accompanied
@@ -434,13 +436,13 @@ by the edges' labels. Finally we append a~special terminator to mark the boundar
 between the code of this vertex and its successor.
 
 \obs
-The canonical encoding is well defined in the sense that two non-isomorphic graphs
+The canonical encoding is well defined in the sense that non-isomorphic graphs always
 receive different encodings. Obviously, encodings of isomorphic graphs can differ,
 depending on the order of vertices and also of the adjacency lists. A~graph
-on~$n$ vertices with $m$~edges is assigned an~encoding of length at most $2n+2m$
-(for each vertex, we record its label and a~single terminator; edges contribute
-by identifiers and labels). This encoding can be constructed in linear time.
-Let us use the encodings for unification of graphs:
+on~$n$ vertices with $m$~edges is assigned an~encoding of length at most $2n+2m$ ---
+for each vertex, we record its label and a~single terminator; edges contribute
+by identifiers and labels. These encodings can be constructed in linear time and
+we will use them for our unification of graphs:
 
 \lemman{Unification of graphs}\id{uniflemma}%
 A~collection~$\C$ of labeled graphs can be partitioned into classes which
@@ -448,33 +450,35 @@ share the same canonical encoding in time $\O(\Vert\C\Vert)$, where $\Vert\C\Ver
 is the total size of the collection, i.e., $\sum_{G\in\C} n(G) + m(G)$.
 
 \para
-When we have to perform a~topological computation on a~collection of graphs
-on $k$~vertices, we first precompute its result for all possible canonical
-encodings (the \df{generic graphs}) and then we use unification to match
-the actual graphs to the generic graphs. This gives us the following theorem:
+When we want to perform a~topological computation on a~collection~$\C$ of graphs
+with $k$~vertices, we first precompute its result for a~collection~$\cal G$ of \df{generic graphs}
+corresponding to all possible canonical encodings on $k$~vertices. Then we use unification to match
+the \df{actual graphs} in~$\C$ to the generic graphs in~$\cal G$. This gives us the following
+theorem:
 
 \thmn{Batched topological computations, Buchsbaum et al.~\cite{buchsbaum:verify}}\id{topothm}%
-Suppose we have a~topological graph computation~$\cal T$ that can be performed in time
+Suppose that we have a~topological graph computation~$\cal T$ that can be performed in time
 $T(k)$ for graphs on $k$~vertices. Then we can run~$\cal T$ on a~collection~$\C$
-of graphs on~$k$ vertices in time $\O(\Vert\C\Vert + (k+s)^{k(2k+1)}\cdot (T(k)+k^2))$,
-where~$s$ is the number of symbols used as vertex/edge labels.
+of labeled graphs on~$k$ vertices in time $\O(\Vert\C\Vert + (k+s)^{k(k+2)}\cdot (T(k)+k^2))$,
+where~$s$ is a~constant depending only on the number of symbols used as vertex/edge labels.
 
 \proof
 A~graph on~$k$ vertices has less than~$k^2/2$ edges, so the canonical encodings of
-all such graphs are shorter than $2k + 2k^2/2 = k(2k+1)$. Each element of the encoding
-is either a~vertex identifier or a~symbol, so it can attain at most $k+s$ possible values.
-We can therefore enumerate all possible encodings, convert them to the collection $\cal G$
-of all generic graphs and run the computation on them in time $\O(\vert{\cal G}\vert \cdot T(k))
-= \O((k+s)^{k(2k+1)}\cdot T(k))$.
-
-We then use the Unification lemma (\ref{uniflemma}) on the union of the collections
-$\C$ and~$\cal G$ to match the generic graphs with the equivalent graphs in~$\C$
+all such graphs are shorter than $2k + 2k^2/2 = k(k+2)$. Each element of the encoding
+is either a~vertex identifier, or a~symbol, or a~separator, so it can attain at most $k+s$
+possible values for some fixed~$s$.
+We can therefore enumerate all possible encodings, convert them to a~collection $\cal G$
+of all generic graphs and run the computation on all of them in time $\O(\vert{\cal G}\vert \cdot T(k))
+= \O((k+s)^{k(k+2)}\cdot T(k))$.
+
+Then we use the Unification lemma (\ref{uniflemma}) on the union of the collections
+$\C$ and~$\cal G$ to match the generic graphs with the equivalent actual graphs in~$\C$
 in time $\O(\Vert\C\Vert + \Vert{\cal G}\Vert) = \O(\Vert\C\Vert + \vert{\cal G}\vert \cdot k^2)$.
 Finally we create a~copy of the generic result for each of the actual graphs.
-If the computation uses pointers to input vertices in its output, this involves
-redirecting them to the actual input vertices, but we can do that by associating
+If the computation uses pointers to the input vertices in its output, we have to
+redirect them to the actual input vertices, but we can do that by associating
 the output vertices that refer to an~input vertex with the corresponding places
-in the encoding of the input graph. The whole output can be generated in time
+in the encoding of the input graph. This way, the whole output can be generated in time
 $\O(\Vert\C\Vert + \Vert{\cal G}\Vert)$.
 \qed