|
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | + |
| 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 | +  |
| 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 | +  |
| 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