From d36b6fbe6f8d13f21d2f14c3d682949ddb072942 Mon Sep 17 00:00:00 2001 From: Martin Mares <mj@ucw.cz> Date: Tue, 21 Sep 2021 12:49:54 +0200 Subject: [PATCH] Graphs: Whitespace cleanup --- om-graphs/graphs.tex | 56 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/om-graphs/graphs.tex b/om-graphs/graphs.tex index 7334c60..410d397 100644 --- a/om-graphs/graphs.tex +++ b/om-graphs/graphs.tex @@ -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: -- GitLab