Range queries - sparse table
Data-structures #data-structuresSparse table is a data structure for range queries. It can answer queries for associative range functions in O(log n)
time and for non-associative range funcions (i.e. min(a,b)) in O(1)
time.
The intuition behind sparse table is that any non-negative number can be uniquely represented as a sum of decreasing powers of two. For example, the number 7
represented in binary as 0111
can be written as 22 + 21 + 20. In a similar fashion, an interval can also be represented as union of intervals that are decreasing power of 2
.
For example, the interval [2, 9)
can be represented as [2, 2+22) U [6, 6 + 21) U [8, 8 + 20)
(2, 9) = [2, 6) + [6, 8) + [8, 9)
The union will contain at most ceil(log2(length of interval))
intervals. In the above example, total intervals = ceil(log(2+9-1)) = ceil(log(10))
The idea behind sparse table is to compute all the range values for intervals which are power of
2
and then combine them to get the actual results.
Creating sparse table
Sparse table stores values in 2D array where each row belongs to an interval. For an array with size N
the largest power of 2
that can fit in the
length can be computed as P=floor(log2(N)) + 1
.
Let’s take the following example to understand how sparse table is created.
We have an array of size N=7
, using the formula mentioned above the no. of rows = 3
. The sparse table is represented by a 2D array, row 0
contains the original array.
For all other rows, we check the interval and then pick the value from previous row at current index
and current index + interval
and apply combination function before storing it in current row.
row 1
represents the interval 21, the power of 2
is the interval value (1 in this case). For (i,j) = (1,0)
, we pick (i - 1)(j) = (0,0)
and (i-1)(j+interval)=(0,1)
.
Similarly, for (2, 0)
, the values at (2-1,0)
and (2-1, 0+2)
are picked and combination function is applied before storing the values at current index.
Query min of range from table
Let’s get the min value in range (2,5)
in the example. First this we have to find the maximum power of 2 this interval can represent, the will
floor(log2(r-l+1))
. Once the row (interval) is determined, then we can look for the range using the following formula
min = minOf(talbe[row][left], table[row][right - 2^row + 1])
For the range (2,5)
, we will get row=2
and minOf(table[2][2], table[2][2])
which is 1
.
- Example
import kotlin.math.*
//sampleStart
fun main() {
// click + to see full script
val arr = intArrayOf(2, 1, 5, 4, 8, 1, 7).toTypedArray()
val rows = log(arr.size.toDouble(), 2.0).toInt()
val minTable = createSparseTable(arr) { a, b -> minOf(a, b) }
val sumTable = createSparseTable(arr) { a, b -> a + b }
println("arr = ${arr.contentToString()}")
(4..6).also {
println("min of $it = ${getMin(minTable, it)}")
}
(2..4).also {
println("sum of $it = ${getSum(sumTable, rows, it)}")
}
}
//sampleEnd
fun getMin(table: Array<Array<Int>>, range: IntRange): Int {
val l = range.first
val right = range.last
val i = log((right - l + 1).toDouble(), 2.0).toInt()
return min(table[i][l], table[i][right.minus(1.shl(i)).plus(1)])
}
fun getSum(table: Array<Array<Int>>, k: Int, range: IntRange): Int {
var sum = 0
var left = range.first
val right = range.last
for(i in k downTo 0) {
if ((1.shl(i)) <= (right - left + 1)) {
sum += table[i][left]
left += 1.shl(i)
}
}
return sum
}
fun createSparseTable(arr: Array<Int>, f: (a: Int, b: Int) -> Int): Array<Array<Int>> {
val columns = arr.size
val rows = floor(log(columns.toDouble(), 2.0)).toInt().plus(1)
val table = Array(rows) { Array(columns) { 0 } }
System.arraycopy(arr, 0, table[0], 0, arr.size)
for (row in 1..rows) {
var column = 0
while (column.plus(1.shl(row)) <= columns) {
table[row][column] = f.invoke(
table[row - 1][column],
table[row - 1][column + (1.shl(row - 1))]
)
column++
}
}
return table
}