没有合适的资源?快使用搜索试试~ 我知道了~
Range Minimum Query and Lowest Common Ancestor[翻译]1
需积分: 0 1 下载量 23 浏览量
2022-08-08
19:42:09
上传
评论
收藏 120KB DOCX 举报
温馨提示
试读
23页
Range Minimum Query and Lowest Common Ancestor[翻译]1
资源详情
资源评论
资源推荐
Range Minimum Query and Lowest Common Ancestor[翻译]
2007-10-23 09:38 by 农夫三拳, 6076 阅读, 13 评论, 收藏, 编辑
Range Minimum Query and Lowest Common Ancesto
r
【原文见 http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=lo
westCommonAncestor】
作者: By danielp
Topcoder Member
翻
译: 农夫三拳@seu
Introduction
Notations
Range Minimum Query (RMQ)
Trivial algorithms for RMQ
A <O(N), O(sqrt(N))> solution
Sparse Table (ST) algorithm
Segment Trees
Lowest Common Ancestor (LCA)
A <O(N), O(sqrt(N))> solution
Another easy solution in <O(N logN, O(logN)>
Reduction from LCA to RMQ
From RMQ to LCA
An <O(N), O(1)> algorithm for the restricted RMQ
Conclusion
Introduction
在一棵树中查找一对结点的最近公共祖先(LCA)的问题在 20 世纪末期已经被仔细的研
究过了,并且它现在已经成为算法中图论的基本算法了。这个问题之所以有趣并不是因为处
理它的算法很有技巧,而是因为它在字符串处理和生物学计算中的广泛应用,例如,当 LCA
和后缀树或者其他树形结构在一起使用时。Harel and Tarjan 是首先深入研究这个问题的
人,他们得出:在对输入树 LCA 进行线性处理后,查询可以在常数时间内得到答案。他们
的工作已经得到了广泛的延伸,这篇教程将展示一些有趣的方法,而它们还也可以用在其他
的问题上。
让我们考虑一个不太抽象的 LCA 的例子:生命树。地球上当前的居住者是由其他物种
进化而来已经是一个不争的事实。这种进化结构可以表示成一棵树,其中节点表示物种,而
它的孩子结点表示从该物种直接进化得到的物种。现在通过在树中查找一些结点的 LCA 把
具有相似特征的物种划分成组,我们可以找出两个物种共同的祖先,并且我们可以知道它们
所拥有的相似特征是来自于那个祖先。
Range Minimum Query(RMQ)被用在数组中用来查找两个指定索引中具有最小值
的元素的位置。我们后面将会看到 LCA 问题可以归约成一个带限制的 RMQ 问题,其中相
邻的数组元素相差 1。
尽管如此, RMQ 并不是仅仅和 LCA 一起用的。当他们在和后缀数组(一个新的数据结
构,它支持和后缀树同等效率的字符串查询,但是使用更少的内存且编码很简单)一起使用
时,在字符串处理中扮演着相当重要的角色。
在这篇教程中,我们将首先讨论 RMQ。我们将给出解决这个问题的多种方法--有一些
速度比较慢但是容易编码,而其他的则更快。在第二部分我们将讨论 LCA 和 RMQ 之间的
关系。首先我们先回顾一下不使用 RMQ 来解决 LCA 的两个简单方法;然后我们将指出 RMQ
和 LCA 问题其实是等价的;并且,最后,我们将看到 RMQ 问题怎样规约成它的限制版本,
并且对于这个特殊情况给出一个最快的算法。
Notations
假设一个算法预处理时间为 f(n),查询时间为 g(n)。这个算法复杂度的标记为<f(n),
g(n)>。我们将用 RMQ
A
(i, j)来表示数组中索引 i 和 j 之间最小值的位置。 u 和 v 的离
树 T 根结点最远的公共祖先用 LCA
T
(u, v)表示。
Range Minimum Query(RMQ)
给定数组 A[0, N-1]找出给定的两个索引间的最小值的位置。
Trivial algorithms for RMQ
对每一对索引(i, j),将 RMQ
A
(i, j)存储在 M[0, N-1][0, N-1]表中。普通的计
算将得到一个<O(N
3
), O(1)> 复杂度的算法。尽管如此,通过使用一个简单的动态规划
方法,我们可以将复杂度降低到<O(N
2
), O(1)>。预处理的函数和下面差不多:
void process1(int M[MAXN][MAXN], int A[MAXN], int N)
{
int i, j;
for (i =0; i < N; i++)
M[i][i] = i;
for (i = 0; i < N; i++)
for (j = i + 1; j < N; j++)
if (A[M[i][j - 1]] < A[j])
M[i][j] = M[i][j - 1];
else
M[i][j] = j;
}
这个普通的算法相当的慢并且使用 O(N
2
)的空间,对于大数据它是无法工作的。
An <O(N), O(sqrt(N))> solution
一个比较有趣的点子是把向量分割成 sqrt(N)大小的段。我们将在 M[0,sqrt(N)-1]为每一个段
保存最小值的位置。
M 可以很容易的在 O(N)时间内预处理。下面是一个例子:
现在让我们看看怎样计算 RMQ
A
(i, j)。想法是遍历所有在区间中的 sqrt(N)段的最
小值,并且和区间相交的前半和后半部分。为了计算上图中的 RMQ
A
(2,7),我们应该比较
A[2], A[M[1]], A[6] 和 A[7],并且获得最小值的位置。可以很容易的看出这个算法
每一次查询不会超过 3 * sqrt(N)次操作。
这个方法最大的有点是能够快速的编码(对于 TopCoder 类型的比赛),并且你可以
把它改成问题的动态版本(你可以在查询中间改变元素)。
Sparse Table (ST) algorithm
一个更好的方法预处理 RMQ 是对 2
k
的长度的子数组进行动态规划。我们将使用数组
M[0, N-1][0, logN]进行保存,其中 M[i][j]是以 i 开始,长度为 2
j
的子数组的最小
值的索引。下面是一个例子
为了计算 M[i][j]我们必须找到前半段区间和后半段区间的最小值。很明显小的片段有这 2
j - 1
长度,因此递归如下:
预处理的函数如下:
void process2(int M[MAXN][LOGMAXN], int A[MAXN], int N)
{
int i, j;
//initialize M for the intervals with length 1
for (i = 0; i < N; i++)
M[i][0] = i;
//compute values from smaller to bigger intervals
for (j = 1; 1 << j <= N; j++)
for (i = 0; i + (1 << j) - 1 < N; i++)
if (A[M[i][j - 1]] < A[M[i + (1 << (j - 1))][j - 1]])
M[i][j] = M[i][j - 1];
else
M[i][j] = M[i + (1 << (j - 1))][j - 1];
}
一旦我们预处理了这些值,让我们看看怎样使用它们去计算 RMQ
A
(i, j)。思路是选择两个
能够完全覆盖区间[i..j]的块并且找到它们之间的最小值。设 k = [log(j - i + 1)].。为
了计算 RMQ
A
(i, j) 我们可以使用下面的公式
So, the overall complexity of the algorithm is <O(N logN), O(1)>。
Segment trees
为了解决 RMQ 问题我们也可以使用线段树。线段树是一个类似堆的数据结构,可以在
基于区间数组上用对数时间进行更新和查询操作。我们用下面递归方式来定义线段树的[i,
j]区间:
� 第一个结点将保存区间[i, j]区间的信息
� 如果 i<j 左右的孩子结点将保存区间[i, (i+j)/2]和[(i+j)/2+1, j] 的信息
注意具有 N 个区间元素的线段树的高度为[logN] + 1。下面是区间[0,9]的线段树:
剩余22页未读,继续阅读
西西里的小裁缝
- 粉丝: 25
- 资源: 292
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
评论0