ST(稀疏表):
Sparse Table is a data structure that answers static Range Minimum Query (RMQ). It is recognized for its relatively fast query and short implementation compared to other data structures.
它是对于静态数据快速查询任意区间最值问题的一种数据结构,数据是静态的,不能修改.
ST一般处理两种问题:
RMQ(Range Minimum Query)问题(区间最值问题):
预处理时间复杂度O(nlog(n)), 查询时间复杂度:O(1)
RMQ问题也可以使用线段树来解决(数据可以是动态的):
预处理时间复杂度:O(nlog(n)),查询时间复杂度:O(log(n))
RGQ(Range Gcd Query)问题(区间最大公约数问题)
ST表使用的情景:
RMQ问题,一般暴力的求一个区间的最值问题,时间复杂度是O(n),单次的查询还可以接受,但是多次需要查询不同的区间的最值,在这个查询里面就要消耗O(n)的时间复杂度,有时候是不能接受的,比如在竞赛里面,那么就是使用ST表了,ST表查询区间的最值的时间复杂度是O(1)
哈哈,前面说了这么多,都是说它有多好,下面正式进入主题,以一个例子说一下
我们有一个数据: 3 1 2 5 4 ,将它放在数组arr中
现在我们需要查询出这个数据的任意一个区间的最大值,使用ST算法进行解决
我们先构造一个ST表,就是一个二维数组,数组的大小为[n][log(n)],n是数据的个数
结果如下图所示
解释一下ST表的含义,s[i][j]代表的是从第i个数据开始,2 ^ j个数字的区间中的最大值,就是数组arr的[i,i+2 ^ j-1]区间的最大值,
根据表的规律,可以得出ST表的初始化公式:s[i][j]=max(s[i][j-1],s[i+1<<(j-1)][j-1])
这个公式也好理解,ST表的行i表示从第i个数开始的区间,列j表示这个区间里面有2 ^ j个数,
要求s[i][j]的值,就是区间[i,i+2 ^ j] 的最大值,
这个区间的最大值不就是 s[ i ][ j-1] (区间[i,i+2 ^ (j-1) -1] 最大值)和
s[i+1<<(j-1)][j-1] (区间[i+2 ^(j-1), i+ 2 ^ j -1]最大值) 这两个数中最大的一个吗?
就是将这个大的区间分成两个小区间的,得到两个小区间的最大值,再在两个值中选择最大的那个,就是这段区间的最大值了
所以ST表后面的状态是继承前面的状态的
对于上面的指数呀,还有log这个函数呀,log是以2为底的(需要转化),这两种数据可以使用这这种方法生成
int[] a=new int[30];
a[0]=1;
for(int x=1;x<a.length;x++)
{
a[x]=a[x-1]*2;
}
int[] log=new int[30];
log[0]=-1;
for(int x=1;x<log.length;x++)
{
log[x]=log[x/2]+1;
}
System.out.println(Arrays.toString(a));
System.out.println(Arrays.toString(log));
前面都是预处理,将ST创建好后,接下来就是查询区间的最大值
我先给出查询的公式再解释
len=(int)(Math.log(right-left+1)/Math.log(2))
最大值=max( s[left][len] , s[right-(1<<len)+1][len])
这个公式应该怎么理解呢?这样的,先明确一下我们的目的,求这个区间的最大值,现在假设是求这五个数据的最大值,我们又要是使用ST表的,但是ST表没有直接存储从第一个元素开始,第五个元素结束的最大值,ST表存储的都是2的次方个数据的区间的最大值
那么我们就想将这个区间分成两部分,而且是两部分的区间的最大值能在ST表里面找到最大值,(这两个区间可能重叠,也可能不重叠,像现在的例子就重叠了)
那现在的问题就是我们要怎么分
我们发现,2 ^ (int)(log(len)) 的值是在len/2到len之间的(这里的len只是表示一个数)
因为 2 ^(log(len)就是len,但是 (int)(log(len)就是将小数点去除了,
但是2 ^ (int)(log(len)) 肯定比len/2大,因为len/2是(log(len)少了一个1
发现这个就好了,我们将区间分成两部分,
前面是:s[left][len] ,这个好理解
后面是:s[right-(1<<len)+1][len]
这个的len也好理解,有人可能想right-(1<<len)+1怎么来的?
就是从后面开始计算,这个小区间有1<<len个数,就是index +(1<<len)-1=right
所以index=right -(1<<len)+1
两个区间加起来就是大的区间,因为这两个区间要么各自就是大区间的一半,要么就是大于大区间的一半
这样子就可以使用O(1)的时间复杂度来查询区间的最大值,应该也说完了(不知道有没有说清楚了,哈哈)