Uploaded by alandy0615

Analysis of Range Query Algorithms

advertisement
Analysis of Range Query Algorithms
Timothy Wei, Alan Lu
Overleaf
June 2023
Objective
Suppose there is an array of integers arr with n ≤ 105 elements.
There are q ≤ 105 queries each asking for the sum of the elements
from index i to j inclusive.
How can we answer the queries quickly?
Table of Contents
Brute Force
Implementation
Complexity Analysis
Prefix Sums
Implementation
Complexity Analysis
Fenwick Trees
Segment Trees
Brute Force
A logical starting point would be to simply process each query by
iterating through the elements in the range.
Implementation
//Input n, the length of the array, and q, the number of queries
cin >> n >> q;
//Get the array a from the input stream
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < q; i++) {
int u, v;
//Get u and v, the endpoints of each query
cin >> u >> v;
int sum = 0;
//Sum elements of a from u to v
for (int j = u; j <= v; j++) {
sum += a[j];
}
cout << sum << ’ ’;
}
Complexity Analysis
Each query iterates through at most n elements, and there are q
queries total, so the overall complexity is O(nq). Not very fast.
Prefix Sums
However, note that we are computing the same sums multiple
times. Consider the two queries (0, 3) and (0, 5). We are
computing the range (0, 3) twice! This motivates us to use the
following optimization:
Theorem
Let prefixSum[i] represent the sum of the numbers from index 0 to
i. The sum of the numbers from index i to j is given by:
▶ If j = 0, the sum is prefixSum[i].
▶ Otherwise, the sum is prefixSum[i] - prefixSum[j - 1].
Prefix Sums Example
For example, suppose we have the following array:
0
5
-3
5
12
-6
8
-1
5
3
Then, the corresponding array prefixSum would be:
0
5
2
7
19
13
21
20
25
28
Now, suppose we want to find the sum of the elements in the
range 3 to 7. Then, we would take prefixSum[7] - prefixSum[3 - 1]
= 20 - 2 = 18.
Implementation
//Some previous code has been ommitted for brevity
prefixSum[0] = a[0];
//Calculate prefixSum
for (int i = 1; i < n; i++) {
prefixSum[i] = prefixSum[i - 1] + a[i];
}
for (int i = 0; i < q; i++) {
int u, v;
//Get u and v, the endpoints of each query
cin >> u >> v;
//Case 1: the first endpoint is 0
if (u == 0) {
cout << prefixSum[v] << ’ ’;
}
//Case 2: both endpoints are non-zero
else {
cout << prefixSum[v] - prefixSum[u - 1] << ’ ’;
}
}
Complexity Analysis
Now, each query is nearly instantaneous at O(1) speed. Since
there are q queries, the total complexity is O(q). Note that there
is also O(n) preprocessing. So, the overall complexity is O(n + q).
Splendid.
Fenwick Trees
We should be feeling pretty good about ourselves now. We can
solve queries in O(1) time! It’s time to add an additional layer of
complexity. Suppose we have another type of query that updates
the value at a certain index to a different value. Then, if we were
to apply prefix sums, we would have to recalculate the prefix sums
for the entire array every time there is an update! This will make
the time complexity O(n2 ), which is really slow. One way to speed
this up is to apply Fenwick Trees.
Fenwick Trees
Consider the following array bit defined in the following way:
Theorem
Suppose bit[i] stores the sum of all numbers in the range (i - (i &
-i), i]. Let query(i) represent the sum in the range [0, i]. Then
query(i) = bit[i] + query(i - (i & -i)).
Proof.
Note that i & -i is the rightmost nonzero bit. For
example, 11 = 10112 , so bit[10112 ] stores the sum in
the range (10102 , 10112 ]. Then bit[10102 ] stores the
sum in (10002 , 10102 ]. Then bit[10002 ] stores the sum
in (0, 10002 ], so adding these together gives us
query(10112 ).
Segment Trees
Suppose that we stored the sum of each half of the array, then
each quarter, then each eighth... and so on. We can draw this as a
tree, as shown:
Updates
Suppose that we wish add 3 to the second element, for example.
What would happen? We would need to add three to all the nodes
shown:
Queries
Ok, that wasn’t too bad. What if we want to find the sum of, for
example, index 1 to 4? We would add the following nodes together:
Notice that we don’t have to go all the way down on the right side,
because we have already stored that sum in the [3-4] range.
Download