In-depth understanding of rmq & LCA
Body
Section 1
Rmq and LCA Overview
LCA: Lowest common ancestor, translated as the recent common ancestor. The explanation is: In a root tree, identify the public ancestor closest to any two nodes in the tree, or find the public ancestor farthest from any two nodes.
Rmq: range minimum query. The explanation is: For Series A with n elements, locate the minimum and minimum values between two specified indexes in the series.
Section 2
Rmq Algorithm
First, let's look at the rqm algorithm. I will introduce several methods to solve this problem based on the pre-processing and query speed.
The array a [n] is provided, and its representation is as follows:
The minimum element of the interval () is required, as shown in:
Solution 1: directly traverse the interval
After seeing this problem, we first thought of traversing these numbers in the interval to find the maximum value of the interval. Therefore, the query time is O (m ). However, when the data volume is very large and the query frequency is very high, the effect of directly traversing the sequence is not that ideal. Because each query has to traverse the sequence once, this obviously cannot meet the requirements for large data volumes. However, this algorithm is a good choice for small data volumes!
Query: O (m ).
The algorithm code is as follows:
Int maxnum = 0; for (I = 0; I <range; I ++) {/** find the maximum value **/If (array [I]> maxnum) {maxnum = array [I] ;}}
Solution 2: Cutting Method
In solution 1, the query speed is O (M). If this is the case for each query, it will become a turtle speed. So we pre-processed the solution. This is what we will talk about in this section: Cutting Method.
First, we divide the sequence into SQRT (n) parts, and use the array M [SQRT (n)] to represent the subscript of the smallest value in each part, that is, the location of this smallest number. For the array m, we only need to traverse the original sequence once to get M. As shown in:
Next we will ask rmq []. To obtain the minimum value of the range [2, 7], we need to compare a [2], a [M [1], a [6], and a [7], and obtain the subscript of the minimum value.
Analysis: in fact, this method has no substantial improvement over the first method, or even worse than method 1. My explanation is:: We compared the query speed. To put it bluntly, we are pursuing the query speed, so as long as the query speed is high, it is also worth doing some preprocessing first (solution 4 is based on this idea).Now let's look at the example above. 2. After preprocessing, we get the array M. At this time, we need the maximum value of the range. Then we only need to be within the range, compare the values that contain the array m and the values that contain the two boundaries. In this way, the number of queries: O (m) <= number of queries <O (m) + K, k <SQRT (n ).
Solution 3: Sorting
Solution 2 has already mentioned that our goal is to find the data quickly, so we can sort the M data in the selected range, and then we can get the minimum value directly. However, if sorting is performed, there will be great defects. Let's take a look.
Analysis: We Choose fast sorting, O (M * logm), but fast sorting will change the relative position of the number in the sequence. Therefore, to ensure that the order of the original data remains unchanged, we have to use the space of O (m) to maintain the original sequence, so this consumption is very large. Note: The Sorting Algorithm with the complexity of O (M * m) won't be too long here! You know!
Query: O (1 ).
OK. The Code is as follows:
Fast sorting int partition (int * array, int low, int high) {intkey = array [High]; Inti = low; intj = high; while (I <j) {While (array [I] <= Key & I <j) {I ++;} array [J] = array [I]; while (array [J]> = Key & I <j) {J --;} array [I] = array [J];} array [I] = key; return I;} void quicksort (int * array, int low, int high) {intindex; Inti = low; intj = high; if (I <j) {Index = partition (array, low, high); quicksort (array, low, index-1); quicksort (array, index + 1, high );}}
After sorting, you can get the most value!
Solution 4: sparse table (ST) Algorithm
The St algorithm is a relatively efficient algorithm for online rmq processing. The so-called online algorithm means that each input query will process the query immediately. The St algorithm first preprocess the sequence, and then it can answer the query.
Analysis:
Preprocessing: O (N * logn ).
Query: O (1). This query is exactly what we want.
Okay. Let me introduce the st Algorithm in detail:
Preprocessing: first, maintain an array M [N] [logn]. The M [I] [J] value starts from the I position of the original sequence, the subscript of the minimum value of 2j consecutive elements is as follows:
So how do we calculate M [I] [J?
We use the DP idea to divide the interval into two parts: M [I] [J-1] and M [I] [2 ^ (J-1)]. Now we only need to compare the two subintervals to get M [I] [J. The comparison rules are as follows:
Therefore, you can write the code as follows:
void Proprocessing(int M[N][logN], int *A, int N){inti, j;for(j = 1; (1 << j) < N; j++){for(i = 0; (i + (1 << j) - 1) < N; i++){if(A[ M[i][j - 1] ] < A[ M[i + (1 << (j - 1))][i - 1]]){M[i][j] = M[i][j - 1];}else{M[i][j] = A[ M[i + (1 << (j - 1))][i - 1]];}}}}
Solution 5: line tree we can also use line tree to solve the rmq problem, if you need to understand the line tree, please visit this tour: line tree: http://en.wikipedia.org/wiki/Segment_tree line tree structure tips:
OK. We constructed a line segment tree based on the phrase and the example above, as shown below:
To apply the line segment tree to the rmq problem, first maintain an array named m with a 2 ^ ([logn] + 1 + 1) element, M [2 ^ ([logn] + 1 + 1: M [I] indicates the position (subscript) of the minimum value of the divided node range ). With this knowledge, we can construct a line segment tree through code, and calculate the array m by the value represented by the node. The Code is as follows:
Void init_tree (INT node, int low, int high, int * array, int * m) {/*** node: indicates a node in the line segment tree *** low: low index ***** high: High Index ***** array: indicates the original array ***** M: indicates maintaining the underlying array ***/If (Low = high) // For the leaf node {M [node] = low;} else {init_tree (2 * node, low, (Low + high)/2, array, m); init_tree (2 * node + 1, (low + high)/2 + 1, high, array, M ); if (array [M [2 * node] <= array [M [2 * node + 1]) // obtain the subscript of a smaller value {M [node] = m [2 * node];} else {M [node] = m [2 * node + 1] ;}}
The complexity of constructing a line segment tree is O (n ). The line segment tree is successfully constructed, and then the query is completed. We know that the time required for line segment tree query is O (logn ). Because we have already learned several operations on the Line Segment tree, we will not go into details here. Let's look at the Code directly!
Int query (INT node, int low, int high, int * a, int * B, int I, Int J) {/*** node: * low: indicates a node in the line segment tree. * low: indicates a low index. * high: indicates a high index. * array: indicates the original array. * m: indicates the maintenance of the underlying array *** I, j: indicates the interval ***/ints, T; if (I> high | j <low) return-1; if (low> = I & high <= J) return B [node]; // returns the subscript S = query (2 * node, low, (Low + high)/2, a, B, I, j); t = query (2 * node + 1, (low + high)/2 + 1, high, a, B, I, j); If (S =-1) return B [node] = T; If (t =-1) return B [node] = s; if (a [s] <= A [T]) return B [node] = s; elsereturn B [node] = T ;}
Section 3 we already know the concept of the LCA algorithm. Let's take a look at its implementation process! For a tree, I use a binary tree here, as shown in. We are looking for the nearest common ancestor of node 8 and node 9, namely Node 2. Note: Some friends say that this problem can be solved as whether the two linked lists are intersecting. We only need to obtain the paths from two nodes to the root node, and these two paths are two linked lists, the problem is solved. Obviously, this is feasible.
Preparations before the war:
Array T [I]: indicates the parent node of a node I in the tree;
Array L [I]: indicates a node I in the tree.
Maintenance array: P [N] [logn]: Where p [I] [J] indicates the J ancestor of the I node in the tree.
The implementation process is as follows:
Use binary search to determine whether node P and node Q are on the same layer of the tree:
If we are at the same layer, we use the DP idea to constantly find the LCA (P = P [p] [J], q = P [Q] [J]), once P = Q is stopped, because the parent nodes of p and q are the same, that is, we have found the recent common ancestor.
If not on the same layer, if P> q, that is, P is relative to Q, P is deeper in the tree. At this point, we still use the DP idea to find the nodes at the same layer of Q and P's ancestor, that is, q = P _'s ancestor. Next we can follow the same layer of practice.
Implementation is so simple.
The first step is to obtain the maintenance array P [N] [logn] Through preprocessing:
void preprocessing(int *t, int n, int p[][max]){inti, j;for(i = 0; i < n; i++)p[i][0] = t[i];for(j = 1; (1 << j) <= n; j++) { for(i = 0; i < n; i++) { if(p[i][j - 1] != -1)p[i][j] = p[p[i][j - 1]][j - 1]; } }}
The following is the query:
int query(int *t, int *l, int s, int t, int n, int p[][max]){inttmp, lg, i;if(l[s] < l[t]){tmp = s;s = t;t = tmp;}for(lg = 1; (1 << lg) <= l[s]; lg++);for(i = lg; i >= 0; i--){if((l[s] - (1 << i)) >= l[t])s = p[s][i];}if(s == t)return s;for(i = lg; i >= 0; i--){if(p[s][i] != -1 && p[s][i] != p[t][i]){s = p[s][i];t = p[t][i];}}return t[s];}
The above mentioned LCA algorithm should be the easiest to think of. the preprocessing process o (nlogn) and query O (logn ). There is also an algorithm similar to rmq, which I will not repeat here first, and I will make up some time later.
Conclusion
Think about, write, and draw .......
Follow-up: the second half of this article takes a long time, so the writing is in a hurry. If there is anything wrong with this article, please correct me!