Jump to content

Level ancestor problem

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Makecat-bot (talk | contribs) at 08:32, 7 February 2013 (r2.7.3) (Robot: Adding th:ปัญหาระดับบรรพบุรุษ). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

In graph theory, the level ancestor problem is the problem of preprocessing a given rooted tree T in order to answer level ancestor queries.[clarification needed] Let T be a rooted tree and let v be a node of T, the level ancestor query LA(v,d) requests the depth d ancestor of node v. The depth of a node v in a tree is the number of edges on the shortest path from the root of the tree to node v.

Level ancestor query

Level ancestor of (v,2) and the path from root r to the node v.

One of the fundamental algorithmic problems on trees is to find level ancestors of nodes. The simplest way to find a level ancestor of a node is to climb up the tree towards the root of the tree. On the path to the root of the tree, every ancestor of a node can be visited and therefore reported. In this case, the tree does not need to be preprocessed and the time to answer a query is O(n). This approach is not feasible in situations where a large number of a queries are required to be processed. Alternatively, all the possible solutions can be precomputed and stored in a table. In this case, the queries can be answered in O(1) but the space and the preprocessing time is O(n2). However, a tree can be preprocessed in O(n) such that every Level Ancestor Query can be answered in O(1). There is a number of known algorithms [1][2] that can optimally pre-process a given tree and answer such queries in constant time. The simplest queries that can be answered in constant time without any preprocessing are LA(v, 0) and LA(v, depth(v)). In the former case, the answer is always the root of the tree and in the latter case, the answer is the node v itself.

Jump pointer algorithm

The jump pointer algorithm [1] is a simple algorithm that pre-processes a tree in O(n log n) time and answers level ancestor queries in O(log n) time. The jump pointer algorithm associates up to log n pointers to each vertex of the tree. These pointers are called jump pointers because they jump up the tree towards the root of the tree. For a given node v of a tree, the algorithm stores an array of length jumpers where . The ith element of this array points to the 2ith ancestor of v. Using such data structure helps us jump half way up the tree from any given node. When the algorithm is asked to process a query, we repeatedly jump up the tree using these pointers. Observe that the number of jumps will be at most log n and therefore queries can be answered in log n time.

Ladder algorithm

Ladder algorithm [1] is based on the idea of simplifying a tree into a bunch paths. The reason for this is the fact that paths are easier to be queried when it comes to level ancestor queries. Consider a path P consisting of n nodes rooted at a node r. We can store the path into an array of size n called Ladder and we can quickly answer a level ancestor query of LA(v, d) by returning Ladder[d] if depth(v)≤d. This will take O(1). However, this will only work if the given tree is a path. Otherwise, we need to decompose it into a bunch of paths. This is done in two stages: long-path decomposition and extending the long paths into ladders.

Stage 1: long-path decomposition

This is a recursive method that decomposes a given tree into a bunch of paths. This stages starts off by finding the longest root-to-leaf path in the tree. It then removes this path by breaking its ties from the tree which will break the remaining of the tree into a bunch of sub-trees and then it recursively processes each sub-tree. Every time a path is decomposed, an array is created in association with the path that contains the elements on the path from the root to the leaf. The base case of this recursion is when the tree is a path in which case its removal leaves an empty graph. Each vertex v has a unique ladder which is the ladder containing it and we call it the "v's ladder". However, after this pre-processing stage, the queries cannot be answered quickly. In fact in order to answer a level ancestor query, the algorithm needs to jump from a path to another until it reaches the root and there could be Θ(√n) of such paths on a leaf-to-root path. This leads us to an algorithm that can pre-process the tree in O(n) time and answers queries in O(√n). In order to reach the optimal query time, we need to process the results in a second stage described below.

Stage 2: extending the long paths into ladders

The first stage of the algorithm decomposes the tree into a number of disjoint paths. In the second stage of the algorithm, each path is extended and therefore the resulting paths will not be mutually exclusive. In the first stage of the algorithm, each path is associated with an array of size h' . We extend this path by adding the h' immediate ancestors at the top of the path in the same array. This will extend each array twice its original size at most which will result in 2n total number of nodes in all the ladders. Notice that the number of ladders is not changed and each node's ladder remains the same. Although a node v can be listed in multiple paths now but its ladder is the one that was associated to v in the first stage of the algorithm. These two stages can be processes in O(n) but the query time is not constant yet. Consider a level ancestor query on a node u of height h. By travelling to the top of u's ladder, a vertex of height at least 2h will be reached. Observe that all the nodes have a height of at least 1 and therefore after doing this i times, we reach a node of height at least 2i and therefore we need to do this at most log n times. This gives us a query time of O(log n).

It turns out that the Ladder algorithm does not do the trick on its own. In fact the Jump pointer algorithm and the Ladder algorithm complement one another. Notice that the two algorithms work in opposite directions. The Jump pointer algorithm makes exponentially increasing hops and the Ladder algorithm makes exponentially decreasing hops and that's the magic. We combine the two algorithms and we can answer queries in O(1) time. We first do a single jump pointer which will take us at least halfway up the tree and then we only need to climb up one ladder in order to answer the query. This results in O(n log n) pre-processing time and O(1) query time.

References

  1. ^ a b c Bender, Michael A. (2004). "The Level Ancestor Problem Simplified". Theor. Comput. Sci. 321: 5–12. doi:10.1016/j.tcs.2003.05.002. {{cite journal}}: Unknown parameter |coauthors= ignored (|author= suggested) (help)
  2. ^ Berkman (1994). "Finding level-ancestors in trees". J. Comput. Syst. Sci. 2. 48: 214–230. doi:10.1016/S0022-0000(05)80002-9. {{cite journal}}: Unknown parameter |coauthors= ignored (|author= suggested) (help); Unknown parameter |month= ignored (help)