Skip to content

Commit

Permalink
tree queries part I
Browse files Browse the repository at this point in the history
  • Loading branch information
emanuele-em committed Nov 7, 2023
1 parent 7ed53ca commit 3afdb3b
Show file tree
Hide file tree
Showing 4 changed files with 379 additions and 3 deletions.
6 changes: 3 additions & 3 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@
- [Strong connectivity](strong_connectivity.md)
- [Kosaraju’s algorithm](kosaraju_s_algorithm.md)
- [2SAT problem](2sat_problem.md)
<!-- - [Tree queries](README.md) -->
<!-- - [Finding ancestors](README.md) -->
<!-- - [Subtrees and paths](README.md) -->
- [Tree queries](tree_queries.md)
- [Finding ancestors](finding_ancestors.md)
- [Subtrees and paths](subtrees_and_paths.md)
<!-- - [Lowest common ancestor](README.md) -->
<!-- - [Offline algorithms](README.md) -->
<!-- - [Paths and circuits](README.md) -->
Expand Down
58 changes: 58 additions & 0 deletions src/finding_ancestors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Finding ancestors

The $k$th **ancestor** of a node $x$ in a rooted tree
is the node that we will reach if we move $k$
levels up from $x$.
Let `ancestor}(x,k)` denote the $k$th ancestor of a node $x$
(or $0$ if there is no such an ancestor).
For example, in the following tree,

<script type="text/tikz">
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (0,3) {1};
\node[draw, circle] (2) at (2,1) {2};
\node[draw, circle] (3) at (-2,1) {4};
\node[draw, circle] (4) at (0,1) {5};
\node[draw, circle] (5) at (2,-1) {6};
\node[draw, circle] (6) at (-3,-1) {3};
\node[draw, circle] (7) at (-1,-1) {7};
\node[draw, circle] (8) at (-1,-3) {8};
\path[draw,thick,-] (1) -- (2);
\path[draw,thick,-] (1) -- (3);
\path[draw,thick,-] (1) -- (4);
\path[draw,thick,-] (2) -- (5);
\path[draw,thick,-] (3) -- (6);
\path[draw,thick,-] (3) -- (7);
\path[draw,thick,-] (7) -- (8);

\path[draw=red,thick,->,line width=2pt] (8) edge [bend left] (3);
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right] (1);
\end{tikzpicture}
</script>

An easy way to calculate any value of `ancestor(x,k)`
is to perform a sequence of $k$ moves in the tree.
However, the time complexity of this method
is $O(k)$, which may be slow, because a tree of $n$
nodes may have a chain of $n$ nodes.

Fortunately, using a technique similar to that
used in Chapter 16.3, any value of `ancestor(x,k)`
can be efficiently calculated in $O(\log k)$ time
after preprocessing.
The idea is to precalculate all values `ancestor(x,k)`
where $k \le n$ is a power of two.
For example, the values for the above tree
are as follows:

| x | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| - | - | - | - | - | - | - | - | - |
| `ancestor(x,1)` | 0 | 1 | 4 | 1 | 1 | 2 | 4 | 7 |
| `ancestor(x,2)` | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 4 |
| `ancestor(x,4)` | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

The preprocessing takes $O(n \log n)$ time,
because $O(\log n)$ values are calculated for each node.
After this, any value of `ancestor(x,k)` can be calculated
in $O(\log k)$ time by representing $k$
as a sum where each term is a power of two.
307 changes: 307 additions & 0 deletions src/subtrees_and_paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
# Subtrees and paths

A **tree traversal array** contains the nodes of a rooted tree
in the order in which a depth-first search
from the root node visits them.
For example, in the tree

<script type="text/tikz">
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (0,3) {1};
\node[draw, circle] (2) at (-3,1) {2};
\node[draw, circle] (3) at (-1,1) {3};
\node[draw, circle] (4) at (1,1) {4};
\node[draw, circle] (5) at (3,1) {5};
\node[draw, circle] (6) at (-3,-1) {6};
\node[draw, circle] (7) at (-0.5,-1) {7};
\node[draw, circle] (8) at (1,-1) {8};
\node[draw, circle] (9) at (2.5,-1) {9};

\path[draw,thick,-] (1) -- (2);
\path[draw,thick,-] (1) -- (3);
\path[draw,thick,-] (1) -- (4);
\path[draw,thick,-] (1) -- (5);
\path[draw,thick,-] (2) -- (6);
\path[draw,thick,-] (4) -- (7);
\path[draw,thick,-] (4) -- (8);
\path[draw,thick,-] (4) -- (9);
\end{tikzpicture}
</script>

a depth-first search proceeds as follows:

<script type="text/tikz">
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (0,3) {1};
\node[draw, circle] (2) at (-3,1) {2};
\node[draw, circle] (3) at (-1,1) {3};
\node[draw, circle] (4) at (1,1) {4};
\node[draw, circle] (5) at (3,1) {5};
\node[draw, circle] (6) at (-3,-1) {6};
\node[draw, circle] (7) at (-0.5,-1) {7};
\node[draw, circle] (8) at (1,-1) {8};
\node[draw, circle] (9) at (2.5,-1) {9};

\path[draw,thick,-] (1) -- (2);
\path[draw,thick,-] (1) -- (3);
\path[draw,thick,-] (1) -- (4);
\path[draw,thick,-] (1) -- (5);
\path[draw,thick,-] (2) -- (6);
\path[draw,thick,-] (4) -- (7);
\path[draw,thick,-] (4) -- (8);
\path[draw,thick,-] (4) -- (9);


\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (2);
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right=15] (6);
\path[draw=red,thick,->,line width=2pt] (6) edge [bend right=15] (2);
\path[draw=red,thick,->,line width=2pt] (2) edge [bend right=15] (1);
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (3);
\path[draw=red,thick,->,line width=2pt] (3) edge [bend right=15] (1);
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (4);
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (7);
\path[draw=red,thick,->,line width=2pt] (7) edge [bend right=15] (4);
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (8);
\path[draw=red,thick,->,line width=2pt] (8) edge [bend right=15] (4);
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (9);
\path[draw=red,thick,->,line width=2pt] (9) edge [bend right=15] (4);
\path[draw=red,thick,->,line width=2pt] (4) edge [bend right=15] (1);
\path[draw=red,thick,->,line width=2pt] (1) edge [bend right=15] (5);
\path[draw=red,thick,->,line width=2pt] (5) edge [bend right=15] (1);

\end{tikzpicture}
</script>

Hence, the corresponding tree traversal array is as follows:

<script type="text/tikz">
\begin{tikzpicture}[scale=0.7]
\draw (0,0) grid (9,1);

\node at (0.5,0.5) {1};
\node at (1.5,0.5) {2};
\node at (2.5,0.5) {6};
\node at (3.5,0.5) {3};
\node at (4.5,0.5) {4};
\node at (5.5,0.5) {7};
\node at (6.5,0.5) {8};
\node at (7.5,0.5) {9};
\node at (8.5,0.5) {5};
%
% \footnotesize
% \node at (0.5,1.4) {1};
% \node at (1.5,1.4) {2};
% \node at (2.5,1.4) {3};
% \node at (3.5,1.4) {4};
% \node at (4.5,1.4) {5};
% \node at (5.5,1.4) {6};
% \node at (6.5,1.4) {7};
% \node at (7.5,1.4) {8};
% \node at (8.5,1.4) {9};
\end{tikzpicture}
</script>

## Subtree queries

Each subtree of a tree corresponds to a subarray
of the tree traversal array such that
the first element of the subarray is the root node.
For example, the following subarray contains the
nodes of the subtree of node $4$:

<script type="text/tikz">
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (4,0) rectangle (8,1);
\draw (0,0) grid (9,1);

\node at (0.5,0.5) {1};
\node at (1.5,0.5) {2};
\node at (2.5,0.5) {6};
\node at (3.5,0.5) {3};
\node at (4.5,0.5) {4};
\node at (5.5,0.5) {7};
\node at (6.5,0.5) {8};
\node at (7.5,0.5) {9};
\node at (8.5,0.5) {5};
%
% \footnotesize
% \node at (0.5,1.4) {1};
% \node at (1.5,1.4) {2};
% \node at (2.5,1.4) {3};
% \node at (3.5,1.4) {4};
% \node at (4.5,1.4) {5};
% \node at (5.5,1.4) {6};
% \node at (6.5,1.4) {7};
% \node at (7.5,1.4) {8};
% \node at (8.5,1.4) {9};
\end{tikzpicture}
</script>

Using this fact, we can efficiently process queries
that are related to subtrees of a tree.
As an example, consider a problem where each node
is assigned a value, and our task is to support
the following queries:

- update the value of a node
- calculate the sum of values in the subtree of a node

Consider the following tree where the blue numbers
are the values of the nodes.
For example, the sum of the subtree of node $4$
is $3+4+3+1=11$.

<script type="text/tikz">
\begin{tikzpicture}[scale=0.9]
\node[draw, circle] (1) at (0,3) {1};
\node[draw, circle] (2) at (-3,1) {2};
\node[draw, circle] (3) at (-1,1) {3};
\node[draw, circle] (4) at (1,1) {4};
\node[draw, circle] (5) at (3,1) {5};
\node[draw, circle] (6) at (-3,-1) {6};
\node[draw, circle] (7) at (-0.5,-1) {7};
\node[draw, circle] (8) at (1,-1) {8};
\node[draw, circle] (9) at (2.5,-1) {9};

\path[draw,thick,-] (1) -- (2);
\path[draw,thick,-] (1) -- (3);
\path[draw,thick,-] (1) -- (4);
\path[draw,thick,-] (1) -- (5);
\path[draw,thick,-] (2) -- (6);
\path[draw,thick,-] (4) -- (7);
\path[draw,thick,-] (4) -- (8);
\path[draw,thick,-] (4) -- (9);

\node[color=blue] at (0,3+0.65) {2};
\node[color=blue] at (-3-0.65,1) {3};
\node[color=blue] at (-1-0.65,1) {5};
\node[color=blue] at (1+0.65,1) {3};
\node[color=blue] at (3+0.65,1) {1};
\node[color=blue] at (-3,-1-0.65) {4};
\node[color=blue] at (-0.5,-1-0.65) {4};
\node[color=blue] at (1,-1-0.65) {3};
\node[color=blue] at (2.5,-1-0.65) {1};
\end{tikzpicture}
</script>

The idea is to construct a tree traversal array that contains
three values for each node: the identifier of the node,
the size of the subtree, and the value of the node.
For example, the array for the above tree is as follows:

<script type="text/tikz">
\begin{tikzpicture}[scale=0.7]
\draw (0,1) grid (9,-2);

\node[left] at (-1,0.5) {node id};
\node[left] at (-1,-0.5) {subtree size};
\node[left] at (-1,-1.5) {node value};

\node at (0.5,0.5) {1};
\node at (1.5,0.5) {2};
\node at (2.5,0.5) {6};
\node at (3.5,0.5) {3};
\node at (4.5,0.5) {4};
\node at (5.5,0.5) {7};
\node at (6.5,0.5) {8};
\node at (7.5,0.5) {9};
\node at (8.5,0.5) {5};

\node at (0.5,-0.5) {9};
\node at (1.5,-0.5) {2};
\node at (2.5,-0.5) {1};
\node at (3.5,-0.5) {1};
\node at (4.5,-0.5) {4};
\node at (5.5,-0.5) {1};
\node at (6.5,-0.5) {1};
\node at (7.5,-0.5) {1};
\node at (8.5,-0.5) {1};

\node at (0.5,-1.5) {2};
\node at (1.5,-1.5) {3};
\node at (2.5,-1.5) {4};
\node at (3.5,-1.5) {5};
\node at (4.5,-1.5) {3};
\node at (5.5,-1.5) {4};
\node at (6.5,-1.5) {3};
\node at (7.5,-1.5) {1};
\node at (8.5,-1.5) {1};
%
% \footnotesize
% \node at (0.5,1.4) {1};
% \node at (1.5,1.4) {2};
% \node at (2.5,1.4) {3};
% \node at (3.5,1.4) {4};
% \node at (4.5,1.4) {5};
% \node at (5.5,1.4) {6};
% \node at (6.5,1.4) {7};
% \node at (7.5,1.4) {8};
% \node at (8.5,1.4) {9};
\end{tikzpicture}
</script>

Using this array, we can calculate the sum of values
in any subtree by first finding out the size of the subtree
and then the values of the corresponding nodes.
For example, the values in the subtree of node $4$
can be found as follows:

<script type="text/tikz">
\begin{tikzpicture}[scale=0.7]
\fill[color=lightgray] (4,1) rectangle (5,0);
\fill[color=lightgray] (4,0) rectangle (5,-1);
\fill[color=lightgray] (4,-1) rectangle (8,-2);
\draw (0,1) grid (9,-2);

\node[left] at (-1,0.5) {node id};
\node[left] at (-1,-0.5) {subtree size};
\node[left] at (-1,-1.5) {node value};

\node at (0.5,0.5) {1};
\node at (1.5,0.5) {2};
\node at (2.5,0.5) {6};
\node at (3.5,0.5) {3};
\node at (4.5,0.5) {4};
\node at (5.5,0.5) {7};
\node at (6.5,0.5) {8};
\node at (7.5,0.5) {9};
\node at (8.5,0.5) {5};

\node at (0.5,-0.5) {9};
\node at (1.5,-0.5) {2};
\node at (2.5,-0.5) {1};
\node at (3.5,-0.5) {1};
\node at (4.5,-0.5) {4};
\node at (5.5,-0.5) {1};
\node at (6.5,-0.5) {1};
\node at (7.5,-0.5) {1};
\node at (8.5,-0.5) {1};

\node at (0.5,-1.5) {2};
\node at (1.5,-1.5) {3};
\node at (2.5,-1.5) {4};
\node at (3.5,-1.5) {5};
\node at (4.5,-1.5) {3};
\node at (5.5,-1.5) {4};
\node at (6.5,-1.5) {3};
\node at (7.5,-1.5) {1};
\node at (8.5,-1.5) {1};
%
% \footnotesize
% \node at (0.5,1.4) {1};
% \node at (1.5,1.4) {2};
% \node at (2.5,1.4) {3};
% \node at (3.5,1.4) {4};
% \node at (4.5,1.4) {5};
% \node at (5.5,1.4) {6};
% \node at (6.5,1.4) {7};
% \node at (7.5,1.4) {8};
% \node at (8.5,1.4) {9};
\end{tikzpicture}
</script>

To answer the queries efficiently,
it suffices to store the values of the
nodes in a binary indexed or segment tree.
After this, we can both update a value
and calculate the sum of values in $O(\log n)$ time.
11 changes: 11 additions & 0 deletions src/tree_queries.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Tree queries

This chapter discusses techniques for
processing queries on
subtrees and paths of a rooted tree.
For example, such queries are:

- what is the $k$th ancestor of a node?
- what is the sum of values in the subtree of a node?
- what is the sum of values on a path between two nodes?
- what is the lowest common ancestor of two nodes?

0 comments on commit 3afdb3b

Please sign in to comment.