Skip to content

Commit e4ce8d3

Browse files
committed
Add Sparse Table
1 parent 2d5d8d0 commit e4ce8d3

11 files changed

+465
-0
lines changed

Sparse Table/Images/idempotency.png

32.9 KB
Loading

Sparse Table/Images/query.png

24.8 KB
Loading

Sparse Table/Images/query_example.png

29.3 KB
Loading

Sparse Table/Images/recursion.png

76.5 KB
Loading
118 KB
Loading

Sparse Table/Images/structure.png

97.8 KB
Loading
154 KB
Loading

Sparse Table/README.markdown

+310
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
# Sparse Table
2+
3+
I'm excited to present **Sparse Tables**. Despite being somewhat niche, Sparse Tables are simple to implement and extremely powerful.
4+
5+
### The Problem
6+
7+
Let's suppose:
8+
- we have an array **a** of some type
9+
- we have some associative binary function **f** <sup>[\*]</sup>. The function can be: min, max, [gcd](../GCD/), boolean AND, boolean OR ...
10+
11+
*<sup>[\*]</sup> where **f** is also "idempotent". Don't worry, I'll explain this in a moment.*
12+
13+
Our task is as follows:
14+
15+
- Given two indices **l** and **r**, answer a **query** for the interval `[l, r)` by performing `f(a[l], a[l + 1], a[l + 2], ..., a[r - 1])`; taking all the elements in the range and applying **f** to them
16+
- There will be a *huge* number **Q** of these queries to answer ... so we should be able to answer each query *quickly*!
17+
18+
For example, if we have an array of numbers:
19+
20+
```swift
21+
var a = [ 20, 3, -1, 101, 14, 29, 5, 61, 99 ]
22+
```
23+
and our function **f** is the *min* function.
24+
25+
Then we may be given a query for interval `[3, 8)`. That means we look at the elements:
26+
27+
```
28+
101, 14, 29, 5, 61
29+
```
30+
31+
because these are the elements of **a** with indices
32+
that lie in our range `[3, 8)` – elements from index 3 up to, but not including, index 8.
33+
We then we pass all of these numbers into the min function,
34+
which takes the minimum. The answer to the query is `5`, because that's the result of `min(101, 14, 29, 5, 61)`.
35+
36+
Imagine we have millions of these queries to process.
37+
> - *Query 1*: Find minimum of all elements between 2 and 5
38+
> - *Query 2*: Find minimum of all elements between 3 and 9
39+
> - ...
40+
> - *Query 1000000*: Find minimum of all elements between 1 and 4
41+
42+
And our array is very large. Here, let's say **Q** = 1000000 and **N** = 500000. Both numbers are *huge*. We want to make sure that we can answer each query really quickly, or else the number of queries will overwhelm us!
43+
44+
*So that's the problem.*
45+
46+
The naive solution to this problem is to perform a `for` loop
47+
to compute the answer for each query. However, for very large **Q** and very large **N** this
48+
will be too slow. We can speed up the time to compute the answer by using a data structure called
49+
a **Sparse Table**. You'll notice that so far, our problem is exactly the same as that of the [Segment Tree](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Segment%20Tree)
50+
(assuming you're familiar). However! ... there's one crucial difference between Segment Trees
51+
and Sparse Tables ... and it concerns our choice of **f**.
52+
53+
54+
### A small gotcha ... Idempotency
55+
56+
Suppose we wanted to find the answer to **`[A, D)`**.
57+
And we already know the answer to two ranges **`[A, B)`** and **`[C, D)`**.
58+
And importantly here, ... *these ranges overlap*!! We have **C** < **B**.
59+
60+
![Overlapping ranges](Images/idempotency.png)
61+
62+
63+
So what? Well, for **f** = minimum function, we can take our answers for **`[A, B)`** and **`[C, D)`**
64+
and combine them!
65+
We can just take the minimum of the two answers: `result = min(x1, x2)` ... *voilà!*, we have the minimum for **`[A, D)`**.
66+
It didn't matter that the intervals overlap - we still found the correct minimum.
67+
But now suppose **f** is the addition operation `+`. Ok, so now we're taking sums over ranges.
68+
If we tried the same approach again, it wouldn't work. That is,
69+
if we took our answers for **`[A, B)`** and **`[C, D)`**
70+
and added them together we'd get a wrong answer for **`[A, D)`**.
71+
*Why?* Well, we'd have counted some elements twice because of the overlap.
72+
73+
Later, we'll see that in order to answer queries, Sparse Tables use this very technique.
74+
They combine answers in the same way as shown above. Unfortunately this means
75+
we have to exclude certain binary operators from being **f**, including `+`, `*`, XOR, ...
76+
because they don't work with this technique.
77+
In order to get the best speed of a Sparse Table,
78+
we need to make sure that the **f** we're using is an **[idempotent](https://en.wikipedia.org/wiki/Idempotence)** binary operator.
79+
Mathematically, these are operators that satisfy `f(x, x) = x` for all possible **x** that could be in **a**.
80+
Practically speaking, these are the only operators that work; allowing us to combine answers from overlapping ranges.
81+
Examples of idempotent **f**'s are min, max, gcd, boolean AND, boolean OR, bitwise AND and bitwise OR.
82+
Note that for Segment Trees, **f** does not have to be idempotent. That's the crucial difference between
83+
Segment Trees and Sparse Tables.
84+
85+
*Phew!* Now that we've got that out of the way, let's dive in!
86+
87+
## Structure of a Sparse Table
88+
89+
Let's use **f** = min and use the array:
90+
91+
```swift
92+
var a = [ 10, 6, 5, -7, 9, -8, 2, 4, 20 ]
93+
```
94+
95+
In this case, the Sparse Table looks like this:
96+
97+
![Sparse Table](Images/structure.png)
98+
99+
What's going on here? There seems to be loads of intervals.
100+
*Correct!* Sparse tables are preloaded with the answers for lots of queries `[l, r)`.
101+
Here's the idea. Before we process our **Q** queries, we'll pre-populate our Sparse Table `table`
102+
with answers to loads of queries;
103+
making it act a bit like a *cache*. When we come to answer one of our queries, we can break the query
104+
down into smaller "sub-queries", each having an answer that's already in the cache.
105+
We lookup the cached answers for the sub-queries in
106+
`table` in constant time
107+
and combine the answers together
108+
to give the overall answer to the original query in speedy time.
109+
110+
The problem is, we can't store the answers for every single possible query that we could ever have ...
111+
or else our table would be too big! After all, our Sparse Table needs to be *sparse*. So what do we do?
112+
We only pick the "best" intervals to store answers for. And as it turns out, the "best" intervals are those
113+
that have a **width that is a power of two**!
114+
115+
For example, the answer for the query `[10, 18)` is in our table
116+
because the interval width: `18 - 10 = 8 = 2**3` is a power of two (`**` is the [exponentiation operator](https://en.wikipedia.org/wiki/Exponentiation)).
117+
Also, the answer for `[15, 31)` is in our table because its width: `31 - 15 = 16 = 2**4` is again a power of two.
118+
However, the answer for `[1, 6)` is *not* in there because the interval's width: `6 - 1 = 5` is *not* a power of two.
119+
Consequently, we don't store answers for *all* possible intervals that fit inside **a**
120+
only the ones with a width that is a power of two.
121+
This is true irrespective of where the interval starts within **a**.
122+
We'll gradually see that this approach works and that relatively speaking, it uses very little space.
123+
124+
A **Sparse Table** is a table where `table[w][l]` contains the answer for `[l, l + 2**w)`.
125+
It has entries `table[w][l]` where:
126+
- **w** tells us our **width** ... the number of elements or the *width* is `2**w`
127+
- **l** tells us the **lower bound** ... it's the starting point of our interval
128+
129+
Some examples:
130+
- `table[3][0] = -8`: our width is `2**3`, we start at `l = 0` so our query is `[0, 0 + 2**3) = [0, 8)`.
131+
The answer for this query is `min(10, 6, 5, -7, 9, -8, 2, 4, 20) = -8`.
132+
- `table[2][1] = -7`: our width is `2**2`, we start at `l = 1` so our query is `[1, 1 + 2**2) = [1, 5)`.
133+
The answer for this query is `min(6, 5, -7, 9) = -7`.
134+
- `table[1][7] = 4`: our width is `2**1`, we start at `l = 7` so our query is `[7, 7 + 2**1) = [7, 9)`.
135+
The answer for this query is `min(4, 20) = 4`.
136+
- `table[0][8] = 20`: our width is `2**0`, we start at `l = 8` so our query is`[8, 8 + 2**0) = [8, 9)`.
137+
The answer for this query is `min(20) = 20`.
138+
139+
140+
![Sparse Table](Images/structure_examples.png)
141+
142+
143+
A Sparse Table can be implemented using a [two-dimentional array](../2D%20Array).
144+
145+
```swift
146+
public class SparseTable<T> {
147+
private var table: [[T]]
148+
149+
public init(array: [T], function: @escaping (T, T) -> T, defaultT: T) {
150+
table = [[T]](repeating: [T](repeating: defaultT, count: N), count: W)
151+
}
152+
// ...
153+
}
154+
```
155+
156+
157+
## Building a Sparse Table
158+
159+
To build a Sparse Table, we compute each table entry starting from the bottom-left and moving up towards
160+
the top-right (in accordance with the diagram).
161+
First we'll compute all the intervals for **w** = 0, then compute all the intervals
162+
and for **w** = 1 and so on. We'll continue up until **w** is big enough such that our intervals are can cover at least half the array.
163+
For each **w**, we compute the interval for **l** = 0, 1, 2, 3, ... until we reach **N**.
164+
This is all achieved using a double `for`-`in` loop:
165+
166+
```swift
167+
for w in 0..<W {
168+
for l in 0..<N {
169+
// compute table[w][l]
170+
}
171+
}
172+
```
173+
174+
To compute `table[w][l]`:
175+
176+
- **Base Case (w = 0)**: Each interval has width `2**w = 1`.
177+
- We have *one* element intervals of the form: `[l, l + 1)`.
178+
- The answer is just `a[l]` (e.g. the minimum of over a list with one element
179+
is just the element itself).
180+
```
181+
table[w][l] = a[l]
182+
```
183+
- **Inductive Case (w > 0)**: We need to find out the answer to `[l, l + 2**w)` for some **l**.
184+
This interval, like all of our intervals in our table has a width that
185+
is a power of two (e.g. 2, 4, 8, 16) ... so we can cut it into two equal halves.
186+
- Our interval with width ``2**w`` is cut into two intervals, each of width ``2**(w - 1)``.
187+
- Because each half has a width that is a power of two, we can look them up in our Sparse Table.
188+
- We combine them together using **f**.
189+
```
190+
table[w][l] = f(table[w - 1][l], table[w - 1][l + 2 ** (w - 1)])
191+
```
192+
193+
194+
![Sparse Table](Images/recursion.png)
195+
196+
For example for `a = [ 10, 6, 5, -7, 9, -8, 2, 4, 20 ]` and **f** = *min*:
197+
198+
- we compute `table[0][2] = 5`. We just had to look at `a[2]` because the range has a width of one.
199+
- we compute `table[1][7] = 4`. We looked at `table[0][7]` and `table[0][8]` and apply **f** to them.
200+
- we compute `table[3][1] = -8`. We looked at `table[2][1]` and `table[2][5]` and apply **f** to them.
201+
202+
203+
![Sparse Table](Images/recursion_examples.png)
204+
205+
206+
207+
```swift
208+
public init(array: [T], function: @escaping (T, T) -> T, defaultT: T) {
209+
let N = array.count
210+
let W = Int(ceil(log2(Double(N))))
211+
table = [[T]](repeating: [T](repeating: defaultT, count: N), count: W)
212+
self.function = function
213+
self.defaultT = defaultT
214+
215+
for w in 0..<W {
216+
for l in 0..<N {
217+
if w == 0 {
218+
table[w][l] = array[l]
219+
} else {
220+
let first = self.table[w - 1][l]
221+
let secondIndex = l + (1 << (w - 1))
222+
let second = ((0..<N).contains(secondIndex)) ? table[w - 1][secondIndex] : defaultT
223+
table[w][l] = function(first, second)
224+
}
225+
}
226+
}
227+
}
228+
```
229+
230+
Building a Sparse Table takes **O(NlogN)** time.
231+
The table itself uses **O(NlgN)** additional space.
232+
233+
## Getting Answers to Queries
234+
235+
Suppose we've built our Sparse Table. And now we're going to process our **Q** queries.
236+
Here's where our work pays off.
237+
238+
Let's suppose **f** = min and we have:
239+
```swift
240+
var a = [ 10, 6, 5, -7, 9, -8, 2, 4, 20 ]
241+
```
242+
And we have a query `[3, 9)`.
243+
244+
1. First let's find the largest power of two that fits inside `[3, 9)`. Our interval has width `9 - 3 = 6`. So the largest power of two that fits inside is four.
245+
2. We create two new queries of `[3, 7)` and `[5, 9)` that have a width of four.
246+
And, we arrange them so that to that they span the whole interval without leaving any gaps.
247+
![Sparse Table](Images/query_example.png)
248+
249+
3. Because these two intervals have a width that is exactly a power of two we can lookup their answers in the Sparse Table using the
250+
entries for **w** = 2. The answer to `[3, 7)` is given by `table[2][3]`, and the answer to `[5, 9)` is given by `table[2][5]`.
251+
We compute and return `min(table[2][3], table[2][5])`. This is our final answer! :tada:. Although the two intervals overlap, it doesn't matter because the **f** = min we originally chose is idempotent.
252+
253+
In general, for each query: `[l, r)` ...
254+
255+
1. Find **W**, by looking for the largest width that fits inside the interval that's also a power of two. Let largest such width = `2**W`.
256+
2. Form two sub-queries of width `2**W` and arrange them to that they span the whole interval without leaving gaps.
257+
To guarantee there are no gaps, we need to align one half to the left and the align other half to the right.
258+
![Sparse Table](Images/query.png)
259+
3. Compute and return `f(table[W][l], table[W][r - 2**W])`.
260+
261+
```swift
262+
public func query(from l: Int, until r: Int) -> T {
263+
let width = r - l
264+
let W = Int(floor(log2(Double(width))))
265+
let lo = table[W][l]
266+
let hi = table[W][r - (1 << W)]
267+
return function(lo, hi)
268+
}
269+
```
270+
271+
Finding answers to queries takes **O(1)** time.
272+
273+
## Analysing Sparse Tables
274+
275+
- **Query Time** - Both table lookups take constant time. All other operations inside `query` take constant time.
276+
So answering a single query also takes constant time: **O(1)**. But instead of one query we're actually answering **Q** queries,
277+
and we'll need time to built the table before-hand.
278+
Overall time is: **O(NlgN + Q)** to build the table and answer all queries.
279+
The naive approach is to do a for loop for each query. The overall time for the naive approach is: **O(NQ)**.
280+
For very large **Q**, the naive approach will scale poorly. For example if `Q = O(N*N)`
281+
then the naive approach is `O(N*N*N)` where a Sparse Table takes time `O(N*N)`.
282+
- **Space**- The number of possible **w** is **lgN** and the number of possible **i** our table is **N**. So the table
283+
has uses **O(NlgN)** additional space.
284+
285+
### Comparison with Segment Trees
286+
287+
- **Pre-processing** - Segment Trees take **O(N)** time to build and use **O(N)** space. Sparse Tables take **O(NlgN)** time to build and use **O(NlgN)** space.
288+
- **Queries** - Segment Tree queries are **O(lgN)** time for any **f** (idempotent or not idempotent). Sparse Table queries are **O(1)** time if **f** is idempotent and are not supported if **f** is not idempotent. <sup>[]</sup>
289+
- **Replacing Items** - Segment Trees allow us to efficiently update an element in **a** and update the segment tree in **O(lgN)** time. Sparse Tables do not allow this to be done efficiently. If we were to update an element in **a**, we'd have to rebuild the Sparse Table all over again in **O(NlgN)** time.
290+
291+
292+
<sup>[]</sup> *Although technically, it's possible to rewrite the `query` method
293+
to add support for non-idempotent functions. But in doing so, we'd bump up the time up from O(1) to O(lgn),
294+
completely defeating the original purpose of Sparse Tables - supporting lightening quick queries.
295+
In such a case, we'd be better off using a Segment Tree (or a Fenwick Tree)*
296+
297+
## Summary
298+
299+
That's it! See the playground for more examples involving Sparse Tables.
300+
You'll see examples for: min, max, gcd, boolean operators and logical operators.
301+
302+
### See also
303+
304+
- [Segment Trees (Swift Algorithm Club)](https://github.com/raywenderlich/swift-algorithm-club/tree/master/Segment%20Tree)
305+
- [How to write O(lgn) time query function to support non-idempontent functions (GeeksForGeeks)](https://www.geeksforgeeks.org/range-sum-query-using-sparse-table/)
306+
- [Fenwick Trees / Binary Indexed Trees (TopCoder)](https://www.topcoder.com/community/data-science/data-science-tutorials/binary-indexed-trees/)
307+
- [Semilattice (Wikipedia)](https://en.wikipedia.org/wiki/Semilattice)
308+
309+
*Written for Swift Algorithm Club by [James Lawson](https://github.com/jameslawson)*
310+

0 commit comments

Comments
 (0)