Commit d36b6fbe authored by Martin Mareš's avatar Martin Mareš
Browse files

Graphs: Whitespace cleanup

parent e39ffd46
......@@ -15,7 +15,7 @@
In this chapter we will peek into the area of data structures for representation of
graphs. Our ultimate goal is to design a data structure that represents a forest with
weighted vertices and allows efficient path queries (e.g. what is the cheapest vertex on the
path between $u$ and $v$) along with cost and structural updates.
path between $u$ and $v$) along with cost and structural updates.
Let us define the problem more formally. We wish to represent a forest $F = (V,
E)$, where each vertex~$v$ has cost~$c(v)\in\R$.\foot{We could also had weighted edges
......@@ -38,7 +38,7 @@ us denote $c_i = c(v_i)$. We build an range
tree~$T$ over the costs $c_1, \dots, c_n$. That is, $T$ is a complete binary tree with
$c_1,\dots c_n$ in its leaves (in this order) and inner nodes contain the minimum of their
children. Note that each node represents a subpath of~$F$ with leaves being the single
vertices.
vertices.
\figure{range-tree.pdf}{}{An example of a range tree for path on eight vertices.
Marked subtrees cover the subpath~$2\to 6$.}
......@@ -52,7 +52,7 @@ and \em{path update} in $\O(\log n)$ time.
Any $v_i \to v_j$ subpath of~$F$ forms
an interval of leaves of~$T$ and each such interval can be exactly covered by $\O(\log n)$
subtrees and they can be easily found by traversing~$T$ top to bottom. The answer to the
path query can then be easily calculated from the values in the roots of these subtrees.
path query can then be easily calculated from the values in the roots of these subtrees.
The point update of $c_i$ is simply a matter of recalculating values in the nodes on path
from root of~$T$ to the leaf~$c_i$, so it takes $\O(\log n)$ time.
......@@ -60,7 +60,7 @@ from root of~$T$ to the leaf~$c_i$, so it takes $\O(\log n)$ time.
The path updates are more tricky. As in the path query, we can decompose the update to
$\O(\log n)$ subtrees. But we cannot afford to recalculate the values in these subtrees
directly as they can contain $\Theta(n)$ nodes. But we can be lazy and let others do the
work for us.
work for us.
Instead of recalculating the whole subtree, we put a \em{mark} into the root along with
the value of~$\delta$. The mark indicates ``everything below should be increased by
......@@ -71,7 +71,7 @@ simply add the new mark to the existing one.
This way, other operations can work as if there were no marks and path updates can be
performed in~$\O(\log n)$ time. Note that this lazy approach requires other operations to
always traverse the tree top-down in order to see correct values in the nodes.
always traverse the tree top-down in order to see correct values in the nodes.
\figure{lazy-update.pdf}{}{Example of range tree traversal with marks. We wish to travel
from $x$ to $z$. The node~$x$ is marked, with $\delta = +4$, so we need to increase value
......@@ -91,7 +91,7 @@ up, towards the root.
Let~$F$ be a rooted tree. For any vertex~$v$ we define $s(v)$ to be the size of subtree
rooted at~$v$ (including~$v$). Let~$u$ be a child of~$v$, we say the edge~$(u,v)$ is
\em{heavy} iff $s(u) \ge s(v)/2$, otherwise we say $(u,v)$ is \em{light}. Finally, a
\em{heavy path} is simply a path containing only heavy edges.
\em{heavy path} is simply a path containing only heavy edges.
}
\obs{
......@@ -105,7 +105,7 @@ Any root-to-leaf path in~$F$ contains at most $\log n$ light edges.
This gives us the decomposition of the tree into heavy paths that are connected via light
edges. The decomposition can be easily found using depth-first search in
linear time.
linear time.
\figure{heavy-light.pdf}{}{Example of heavy-light decomposition. Top part shows a tree
with heavy paths marked by thick lines. Numbers in parenthesis show the value of $s(v)$
......@@ -148,9 +148,9 @@ be divided into two top-down paths at the lowest common ancestor of $x$ and $y$.
We represent each heavy path using the range tree structure for static path from the
previous chapter. The root of each range tree will also store the light edge that leads up
from the top of the path and connects it to the rest of the tree. We also need to store
the extra information used in the LCA algorithm.
the extra information used in the LCA algorithm.
Both queries and updates are then evaluated per partes.
Both queries and updates are then evaluated per partes.
We partition the query (update) into $\O(\log n)$ queries on heavy paths plus
$\O(\log n)$ light edges. To do so, we need to calculate LCA of query path's endpoints
which takes $\O(\log n)$ time. Each subquery can be evaluated in $\O(\log n)$ time and so
......@@ -193,9 +193,9 @@ only in amortized case, it is significantly easier to analyze.
Link-cut tree represents a forest $F$ of \em{rooted} trees; each edge is oriented towards the
respective root. It supports following operations:
\list{o}
\: Structural queries:
\: Structural queries:
\tightlist{-}
\:$\opdf{Parent}(v)$;
\:$\opdf{Parent}(v)$;
\:$\opdf{Root}(v)$ --- return root of $v$'s tree
\endlist
\: Structural updates:
......@@ -242,7 +242,7 @@ a constant number of operations on a fat path:
\:$\op{PathUpdate}(v)$ = $\Expose(v)$, then perform path update on the fat path.
\endlist{}
Conceptually, $\Expose(v)$ is straightforward.
Conceptually, $\Expose(v)$ is straightforward.
At the beginning,
we turn a fat edge below~$v$ into thin edge (if there was one) and make~$v$ the endpoint
of the fat path.
......@@ -252,7 +252,7 @@ we are done), $t$ is connected to a fat path~$B$ via thin edge $(t, p)$, see
Figure~\figref{expose-idea}. We cut~$B$ by turning fat edge below~$p$ into a thin edge.
Then we join top half of~$B$ with~$A$ by making edge~$(t, p)$ fat. This is one step of the
$\Expose$. Now
we jump to the top of the newly created fat path and repeat the whole process.
we jump to the top of the newly created fat path and repeat the whole process.
\figure[expose-idea]{expose-idea.pdf}{}{One step of $\Expose$ in the thin-fat
decomposition.}
......@@ -265,7 +265,7 @@ By using a balanced binary tree to represent fat paths, we obtain
$\O(\log^2 n)$ amortized time complexity for $\Expose$ and all other operations. But we
can do better! In the original paper, Sleator and Tarjan use biased binary trees to
improve the bound to $\O(\log n)$ amortized and even show that the construction can be
deamortized to obtain this complexity in the worst case.
deamortized to obtain this complexity in the worst case.
However, this construction is
quite technical and complicated. Instead, we use splay trees to represent fat paths. This
yields $\O(\log n)$ amortized complexity of $\Expose$, but with significantly easier
......@@ -314,7 +314,7 @@ to recalculate minima during rotation. But this can be easily done in a constant
With this representation, we can easily answer ordering queries like previous vertex on
the path, first and last vertex on the path etc. in an amortized $\O(\log n)$ time. We
just have to make sure to splay the deepest node we touch during the operation.
just have to make sure to splay the deepest node we touch during the operation.
Path minimum queries can also be easily answered in amortized $\O(\log n)$. Recall that we
only need to answer path queries only for a prefix of the fat path, since
......@@ -330,11 +330,11 @@ lazily using the same trick as in the path update. Each node of the tree contain
bit to indicate that the whole subtree below has switched orientation. As with the path
updates, we propagate and clean the bits during rotations. Note that this
switch is relative to the orientation of the parent node. Thus, in order to know the absolute
orientation, we need to always travel in the direction from root to the leaves.
orientation, we need to always travel in the direction from root to the leaves.
\subsection{Representation of trees}
Now that we know how the fat paths are represented, we need to add thin edges and connect
fat paths into trees. And this is exactly what we do.
fat paths into trees. And this is exactly what we do.
Apart from left and right son,
we allow each node~$v$ in a splay tree to have multiple \em{middle sons}. Middle sons
......@@ -348,11 +348,11 @@ node to its middle son. We only store a pointer from the middle son to its paren
need to update this pointer when the root of the subordinate splay tree changes, as we
require the middle son to be the root of the splay tree. On the other hand, rotations in
parent's splay tree have no influence on the middle sons. Also note that lazy updates
(from path updates and path reversals) are \em{not} propagated to middle sons.
(from path updates and path reversals) are \em{not} propagated to middle sons.
This way, each tree is represented by one \em{virtual tree}, where virtual tree contains
two types of edges. One type represent the edges in splay trees and the other represents
thin edges and middle sons.
thin edges and middle sons.
\figure[middle-sons]{middle-sons.pdf}{}{Example of a tree decomposed into a system of fat
paths and a possible corresponding virtual tree.}
......@@ -367,7 +367,7 @@ path~$A'$. Thus, we just turn left son of~$v$ into a middle son.
Now we show a single step of $\Expose(v)$. Vertex~$v$ is the bottom node
of a fat path~$A$ which is connected via a thin edge to the vertex~$p$ on a fat path~$B$.
Both~$A$ and~$B$ are represented by a splay trees $T_A$ and $T_B$ respectively. We assume
$v$ is the root of~$T_A$, since we splayed it during the initial phase.
$v$ is the root of~$T_A$, since we splayed it during the initial phase.
Similarly to the initial phase of $\Expose(v)$, we need to cut the path~$B$ below the
vertex~$p$. So we splay~$p$ and once $p$ is the root of~$T_B$, we turn its left son into a
......@@ -397,7 +397,7 @@ unless we break potential in previous phases, of course. Real cost of the second
be bounded by
the real cost of the third phase, so the third phase can also pay for the second phase.
However, we somehow have to make sure that swapping thin edges and splay edges does not
break the potential stored in the respective trees.
break the potential stored in the respective trees.
The first phase, on the other hand, performs a series of splay operation and each of them
possible costs up to $\Omega(\log n)$ amortized, if we use the basic bound on the
......@@ -413,7 +413,7 @@ of nodes in the subtree below~$v$ \em{including all nodes reachable through midd
We define a potential~$\Phi$ of the virtual tree~$T$ to be the sum of the potential of each
splay tree. This also means that $\Phi = \sum_{v\in T} r(v)$, where $r(v) = \log(s(v))$ is
the rank of~$v$.
the rank of~$v$.
Let us star with the third phase. We have only a single splay in a splay tree, so
according to Access lemma we get amortized complexity $\O(r(p_k) - r(v) + 1)$, where the ranks
......@@ -437,7 +437,7 @@ the third phase can save us. Notice that $\O(k)$ can be upper-bounded by the rea
the third phase. Thus, third phase will also pay for the $+k$ part of the first phase.
There is one last subtle problem we have to deal with. The problem is that structural
updates can change the potential of the virtual tree outside of the $\Expose$.
updates can change the potential of the virtual tree outside of the $\Expose$.
The simple case is $\op{Cut}$. Since we remove a subtree from the virtual tree, we only
decrease the potential in the original virtual tree and the potential of the new tree is
paid by the potential of the removed subtree.
......@@ -454,7 +454,7 @@ To show a non-trivial application of link-cut trees we describe how they can be
make faster Dinic's algorithm. Recall that Dinic's algorithm is an algorithm to find a
maximum flow from source vertex~$s$ to a targer vertex~$t$ in network~$G$. We won't
describe the algorithm in full detail here and we focus only on the parts important for
our application of link-cut trees.
our application of link-cut trees.
The key part of Dinic's algorithm is to find a \em{blocking flow} in the \em{level graph}.
A \em{blocking flow} is a flow satisfying the property that every (directed) path from source
......@@ -466,13 +466,13 @@ flow. Capacity of each edge in residual network is exactly the residual capacity
important property of level graph is that it is acyclic and it can be decomposed into
levels such that there are no edges between vertices in each level, see
Figure~\figref{level-network} level
graph.
graph.
\figure[level-network]{level-network.pdf}{}{Example of a level network.}
Dinic's algorithm starts with a zero flow and in each iteration it finds a blocking flow
in the level graph and augments the flow with the blocking flow. It can be shown that we
need~$n$ iterations to find the maximum flow in~$G$.
need~$n$ iterations to find the maximum flow in~$G$.
Traditionally, the search for the blocking flow is done greedily in $\O(nm)$ time, which
results in $\O(n^2m)$ complexity of the whole algorithm (construction of the leveled graph
......@@ -482,7 +482,7 @@ achieve $\O(m\log n)$ time per iteration.
We use link-cut trees with weighted edges -- the cost of an edge is its residual capacity.
At the beginning of iteration each vertex in the level graph arbitrarily selects a single
outgoing edge. These edges form a forest~$F$ oriented towards~$t$ and we build a link-cut
tree over~$F$. We also mark the edges of~$F$ as processed.
tree over~$F$. We also mark the edges of~$F$ as processed.
\def\Cost{\op{Cost}}
......@@ -500,7 +500,7 @@ simply cut~$e$ and remove it from $F$.
\proc{Augment step}
\:If $\Root(s) = t$, then:
\::$e \= \op{PathMin}(s)$
\::$e \= \op{PathMin}(s)$
\::If $\Cost(e) = 0$:
\:::$\op{Cut}(e)$
\::else:
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment