ST表的原理及其实现

Posted qq965921539

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ST表的原理及其实现相关的知识,希望对你有一定的参考价值。

ST表类似树状数组,线段树这两种算法,是一种用于解决RMQ(Range Minimum/Maximum Query,即区间最值查询)问题的离线算法

与线段树相比,预处理复杂度同为O(nlogn),查询时间上,ST表为O(1),线段树为O(nlogn)

st表的主体是一个二维数组st[i][j],表示需要查询的数组的从下标i到下标i+2^j - 1的最值,这里以最小值为例

预处理函数:

 1 int a[1010];//原始输入数组
 2 int st[1010][20];//st表
 3 
 4 void init(int n)
 5 {
 6     for (int i = 0; i < n; i++)
 7         st[i][0] = a[i];
 8 
 9     for (int j = 1; (1 << j) <= n; j++)
10     {
11         for (int i = 0; i + (1 << j) - 1 < n; i++)
12             st[i][j] = min(st[i][j - 1],st[i + (1 << (j - 1))][j - 1]);
13     }
14 }

这里首先把从0~n-1的2^0部分进行覆盖,再往下继承

继承这里也很好理解,我们以一个长度为5的数组[5,1,2,3,4]为例

2^0部分覆盖过去自然是5,4,3,2,1

2^1部分的长度为4,从0一直到3,因为从下标为4开始后面只有他自己

st[0][1]是下标为0~1的最小值,自然也就是st[0][0]和st[1][0]的最值

以此往下类推我们可以得出结论:

st[i][j] = min(st[i][j - 1],st[i + 2^(j - 1))][j - 1])

到这里初始化就完成了,注意下标不要越界,如果你对为什么这么处理有困惑的话,请继续看查询

查询函数这里不太好理解

初始化时,每一个状态对应的区间长度都为2^j,由于给出的查询区间长度不一定恰好为2^j,

所以我们要引出一个定理:2^log(a)>a/2 。

https://blog.csdn.net/Hanks_o/article/details/77547380 这里有一段非常非常好理解的解释,这里超级感谢原作者,我本人不能做出更好的解释,他的讲解是这样的:

这个很简单,因为log(a)表示小于等于a的2的最大几次方。 
比如说log(4)=2,log(5)=2,log(6)=2,log(7)=2,log(8)=3,log(9)=3……. 
那么我们要查询x到y的最小值。 
设len=y-x+1,t=log(len) 
根据上面的定理:2^t>len/2 
从位置上来说,x+2^t越过了x到y的中间! 
因为位置过了一半 
所以x到y的最小值可以表示为min(从x往后2^t的最小值,从y往前2^t的最小值) 
前面的状态表示为mn[t][x] 
设后面(从y往前2^t的最小值)的初始位置是k, 
那么k+2^t-1=y,所以k=y-2^t+1 
所以后面的状态表示为mn[t][y-2^t+1] 
所以x到y的最小值表示为min(mn[t][x],mn[t][y-2^t+1]),所以查询时间复杂度是O(1)

查询函数:

1 int search(int l, int r)
2 {
3     int k = (int)(log((double)(r - l + 1)) / log(2.0));
4     return min(st[l][k],st[r - (1 << k) + 1][k]);
5 }

 示例程序:

 1 #include <iostream>
 2 #include <algorithm>
 3 
 4 using namespace std;
 5 
 6 int a[1010];//原始输入数组
 7 int st[1010][20];//st表
 8 
 9 void init(int n)
10 {
11     for (int i = 0; i < n; i++)
12         st[i][0] = a[i];
13 
14     for (int i = 1; (1 << i) <= n; i++)
15     {
16         for (int j = 0; j + (1 << i) - 1 < n; j++)
17             st[j][i] = min(st[j][i - 1],st[j + (1 << (i - 1))][i - 1]);
18     }
19 }
20 
21 int search(int l, int r)
22 {
23     int k = (int)(log((double)(r - l + 1)) / log(2.0));
24     return min(st[l][k],st[r - (1 << k) + 1][k]);
25 }
26 
27 int main()
28 {
29     int n,m;
30     while (cin >> n >> m)
31     {
32         for (int i = 0; i < n; i++)
33             cin >> a[i];
34 
35         init(n);
36 
37         while (m--)
38         {
39             int l, r;
40             cin >> l >> r;
41             cout << search(l,r) << endl;;
42         }
43     }
44     return 0;
45 }

 这里有一个HDU3183的例题大家可以移步看一下具体的使用

https://www.cnblogs.com/qq965921539/p/9609015.html

以上是关于ST表的原理及其实现的主要内容,如果未能解决你的问题,请参考以下文章

带你彻底击溃跳表原理及其Golang实现!(内含图解)

基于邻接表的拓扑排序实现

认识链表以及其常见操作Java代码实现

顺序表详解及其c语言代码实现

RMQ问题 - ST表的简单应用

Java数据结构(线性表)--线性表的顺序存储及其实现