序列的两个项目之间的最小值
Minimum value between 2 items of a sequence
我正在尝试制作一个程序,该程序将在 O(N) 时间内将数字序列保存在数组中,以便快速回答 (O(logn)) 以下问题。
min(int i,int j): Returns 最小值在位置i和j之间的序列中的位置。
例如,如果序列是 A = (22, 51, 83, 42, 90, 102, 114, 35) 并且我调用 min(3,6)
它将 return 4 因为 42< 83,90,102.
我知道如果序列的值没有排序就不可能实现快速时间,因为我想实现 O(logn) 我想到了实现二叉树。
问题是我无法弄清楚应该以何种方式将序列的值放在二叉树中以便快速访问它们以便 min() 按我的需要工作。
这是典型的用区间树求解的问题。您可以在 O(n)
时间内构造它,然后在 O(log n)
.
中查询 运行
一般的想法是将一个完美的二叉树存储在一个数组中,其中索引 i
处的节点在索引 2i
和 2i+1
处有其子节点。在叶子中,您存储序列的值,并为每个 non-leaf 节点存储其所有后代的最小值。如果你从叶子向上构建树,你可以在 O(n)
时间内完成。
要运行一个间隔[a; b]
的查询,你可以采用两种基本方法(都在O(log n)
时间内工作):
- 从叶到根
a
和b
- 从根开始递归向下树
您可以在 Internet 上的 'interval tree' 短语下轻松找到对这两种方法的描述。对于你的问题,我绝对推荐前者,因为它应该更快一些。
根据要求,我用查询树的说明扩展了我的答案。让我们仔细看看我为您的问题建议的 bottom-up 方法。我将假设数组的索引从 0
到 n - 1
。我还假设 n
等于 2^k
对于某些自然 k
。如果不是,则在查询最小值的情况下,通过在底层末尾添加 +Inf
元素将其增加到最接近的 2 的幂。它不会影响任何有效的查询,并且您会得到一个完美的二叉树,可以像我之前描述的那样轻松索引。为了舒适的实施,我建议使用索引 1 作为根,并且在本说明中也假设了这一点。
这张图应该更清楚了。底部的黑色索引是原始数组中的索引。每个节点旁边的绿色索引是树中的索引。现在忽略矩形,因为它们属于查询示例。
query(a, b)
我们将表示对区间 [a; b]
(含)中的最小值的查询。首先,一个特殊情况:当 a
等于 b
时,我们只是 return tree[n + a]
(请注意,当 tree[1]
是 tree[1]
时,这是正确的索引根)。
让我们转向更复杂的情况 a != b
。该算法的思路是,我们可以将任意区间拆分成O(log n)
个没有共同元素的基本区间,完全覆盖原区间。每个基本区间的大小是 2 的幂,每个基本区间由我们的一个节点表示。当我们列出所有相关区间时,我们只需要取其节点的最小值即可得到 query(a, b)
.
的答案
下面我们将介绍选择基区间的方法。在示例图像中,它们都被矩形包围。看看下面的代码片段:
int x = a + n;
int y = b + n;
int result = Math.min(tree[x], tree[y]);
while (x / 2 != y / 2) {
if (x % 2 == 0) {
result = Math.min(result, tree[x + 1]);
}
if (y % 2 == 1) {
result = Math.min(result, tree[y - 1]);
}
x /= 2;
y /= 2;
}
首先我们将原始索引转换为树中的索引。然后我们会考虑包含查询边界的 single-item 区间。请记住,我已经排除了 a == b
.
时的特殊情况
算法如下进行,向上移动树。每当 x % 2 == 0
时,我们都会考虑树中 x
的兄弟区间。请检查此兄弟是否始终完全包含在区间 [a; b]
中。我们对 y % 2 == 1
做同样的事情,除了兄弟姐妹在 y
的左边。当 x / 2 == y / 2
意味着 x
和 y
现在是兄弟姐妹,我们应该停止算法。您可以自行检查此方法是否以完全覆盖 [a; b]
.
的方式选择间隔
请注意,我们最多可以检查树底层的 4 个节点。在每个级别上,我们将检查不超过 2 个节点。由于树有 O(log n)
层,我们可以看到任何查询的时间复杂度为 O(log n)
.
奖励 - 修改数组。您描述的问题不需要修改数组,但在基本情况下它非常干净,我将在此处添加它。如果您还想处理 set(a, v)
指令,这意味着 array[a] = v
您可以在 O(log n)
时间内轻松完成。首先你设置 tree[a + n] = v
然后你去根更新你路径上的最小值。
我正在尝试制作一个程序,该程序将在 O(N) 时间内将数字序列保存在数组中,以便快速回答 (O(logn)) 以下问题。
min(int i,int j): Returns 最小值在位置i和j之间的序列中的位置。
例如,如果序列是 A = (22, 51, 83, 42, 90, 102, 114, 35) 并且我调用 min(3,6) 它将 return 4 因为 42< 83,90,102.
我知道如果序列的值没有排序就不可能实现快速时间,因为我想实现 O(logn) 我想到了实现二叉树。
问题是我无法弄清楚应该以何种方式将序列的值放在二叉树中以便快速访问它们以便 min() 按我的需要工作。
这是典型的用区间树求解的问题。您可以在 O(n)
时间内构造它,然后在 O(log n)
.
一般的想法是将一个完美的二叉树存储在一个数组中,其中索引 i
处的节点在索引 2i
和 2i+1
处有其子节点。在叶子中,您存储序列的值,并为每个 non-leaf 节点存储其所有后代的最小值。如果你从叶子向上构建树,你可以在 O(n)
时间内完成。
要运行一个间隔[a; b]
的查询,你可以采用两种基本方法(都在O(log n)
时间内工作):
- 从叶到根
a
和b
- 从根开始递归向下树
您可以在 Internet 上的 'interval tree' 短语下轻松找到对这两种方法的描述。对于你的问题,我绝对推荐前者,因为它应该更快一些。
根据要求,我用查询树的说明扩展了我的答案。让我们仔细看看我为您的问题建议的 bottom-up 方法。我将假设数组的索引从 0
到 n - 1
。我还假设 n
等于 2^k
对于某些自然 k
。如果不是,则在查询最小值的情况下,通过在底层末尾添加 +Inf
元素将其增加到最接近的 2 的幂。它不会影响任何有效的查询,并且您会得到一个完美的二叉树,可以像我之前描述的那样轻松索引。为了舒适的实施,我建议使用索引 1 作为根,并且在本说明中也假设了这一点。
这张图应该更清楚了。底部的黑色索引是原始数组中的索引。每个节点旁边的绿色索引是树中的索引。现在忽略矩形,因为它们属于查询示例。
query(a, b)
我们将表示对区间 [a; b]
(含)中的最小值的查询。首先,一个特殊情况:当 a
等于 b
时,我们只是 return tree[n + a]
(请注意,当 tree[1]
是 tree[1]
时,这是正确的索引根)。
让我们转向更复杂的情况 a != b
。该算法的思路是,我们可以将任意区间拆分成O(log n)
个没有共同元素的基本区间,完全覆盖原区间。每个基本区间的大小是 2 的幂,每个基本区间由我们的一个节点表示。当我们列出所有相关区间时,我们只需要取其节点的最小值即可得到 query(a, b)
.
下面我们将介绍选择基区间的方法。在示例图像中,它们都被矩形包围。看看下面的代码片段:
int x = a + n;
int y = b + n;
int result = Math.min(tree[x], tree[y]);
while (x / 2 != y / 2) {
if (x % 2 == 0) {
result = Math.min(result, tree[x + 1]);
}
if (y % 2 == 1) {
result = Math.min(result, tree[y - 1]);
}
x /= 2;
y /= 2;
}
首先我们将原始索引转换为树中的索引。然后我们会考虑包含查询边界的 single-item 区间。请记住,我已经排除了 a == b
.
算法如下进行,向上移动树。每当 x % 2 == 0
时,我们都会考虑树中 x
的兄弟区间。请检查此兄弟是否始终完全包含在区间 [a; b]
中。我们对 y % 2 == 1
做同样的事情,除了兄弟姐妹在 y
的左边。当 x / 2 == y / 2
意味着 x
和 y
现在是兄弟姐妹,我们应该停止算法。您可以自行检查此方法是否以完全覆盖 [a; b]
.
请注意,我们最多可以检查树底层的 4 个节点。在每个级别上,我们将检查不超过 2 个节点。由于树有 O(log n)
层,我们可以看到任何查询的时间复杂度为 O(log n)
.
奖励 - 修改数组。您描述的问题不需要修改数组,但在基本情况下它非常干净,我将在此处添加它。如果您还想处理 set(a, v)
指令,这意味着 array[a] = v
您可以在 O(log n)
时间内轻松完成。首先你设置 tree[a + n] = v
然后你去根更新你路径上的最小值。