范围内的Kth最小值

时间:2014-02-17 10:07:35

标签: algorithm data-structures tree

给定一个整数数组和一些查询操作 查询操作有两种类型 1.将第i个索引的值更新为x。
2.给2个整数找到该范围内的第k个最小值。(例如,如果2个整数是i和j,我们必须找出i和j之间的第k个最小值)。
我可以使用分段树找到范围最小查询但是对于第k个最小值不能这样做。 任何人都可以帮助我吗?

4 个答案:

答案 0 :(得分:8)

以下是每个查询解决方案的O(polylog n)实际上不会假定常量k,因此k可能因查询而异。主要思想是使用一个分段树,其中每个节点代表一个数组索引的间隔,并包含一个代表数组段中值的多集(平衡二叉搜索树)。更新操作非常简单:

  1. 从叶子(您正在更新的数组索引)向上走分段树。您将遇到表示包含更新索引的数组索引间隔的所有节点。在每个节点上,从多集中删除旧值并将新值插入到多集中。复杂性:O(log^2 n)
  2. 更新阵列本身。
  3. 我们注意到每个数组元素都在O(log n)多个集合中,因此总空间使用量为O(n log n)。通过多线程的线性时间合并,我们也可以在O(n log n)中构建初始的分段树(每个级别有O(n)个工作)。

    查询怎么样?我们获得了范围[i, j]和等级k,并希望找到a[i..j]中的第k个最小元素。我们怎么做?

    1. 使用标准段树查询过程查找查询范围的不相交的覆盖范围。我们得到O(log n)个不相交的节点,其多重集合的联合正是查询范围中多个值的集合。我们称之为多重集s_1, ..., s_mm <= ceil(log_2 n))。查找s_i需要O(log n)次。
    2. s_1, ..., s_m的联合进行select(k)查询。见下文。
    3. 那么选择算法如何工作?有一个非常简单的算法可以做到这一点。

      我们已提供s_1, ..., s_nk,并希望找到x中最小的a,以便s_1.rank(x) + ... + s_m.rank(x) >= k - 1,其中rank返回在各个BBST中小于x的元素数量(如果我们存储子树大小,则可以在O(log n)中实现)。 我们只是使用二进制搜索来查找x!我们遍历根的BBST,执行几个排名查询并检查它们的总和是否大于或等于k。它是x中的谓词单调,因此二进制搜索有效。答案是x中任何s_iO(n log n)的后继者的最小值。

      复杂性O(log^3 n)每次查询预处理和O(n log n + q log^3 n)

      因此,我们总共为q个查询获得了O(q log^2 n)的运行时。我确信我们可以通过更聪明的选择算法将其降低到O((n + q) * log n * log (q + n))

      更新:如果我们正在寻找可以同时处理所有查询的离线算法,我们可以使用以下算法获取q + n

      • 预处理所有查询,创建一组在数组中出现的所有值。这些数量最多为m
      • 构建一个分段树,但这次不在数组上,而是在可能的值集上。
      • 段树中的每个节点都代表一个值的间隔,并维护一组出现这些值的位置。
      • 要回答查询,请从细分树的根目录开始。检查根的左子节点中有多少位置位于查询间隔中(我们可以通过在位置的BBST中进行两次搜索来实现)。让这个数字为k <= m。如果k,则递归到左边的孩子身上。否则,递归到正确的孩子,m递减O(log (q + n))
      • 要进行更新,请从涵盖旧值的set<int>节点中删除该位置,然后将其插入覆盖新值的节点中。

      这种方法的优点是我们不需要子树大小,因此我们可以使用平衡二叉搜索树的大多数标准库实现(例如C ++中的{{1}})来实现这一点。

      我们可以通过更改weight-balanced tree such as a BB[α] tree的细分树来将其转换为在线算法。它具有与其他平衡二进制搜索树一样的对数运算,但允许我们通过将重建成本计入必然导致不平衡的操作来重建不整平的整个子树。

答案 1 :(得分:7)

如果这是编程竞赛问题,那么您可能能够使用以下O(n log(n)+ q n ^ 0.5 log(n)^ 1.5)-time算法。它被设置为使用C ++ STL并且具有比Niklas(之前的?)答案更好的大O常数,因为它使用了更少的空间和间接。

将数组划分为长度为n / k的k个块。将每个块复制到第二个阵列的相应位置并对其进行排序。要更新:将更改的块复制到第二个数组并再次排序(时间O((n / k)log(n / k))。要查询:复制到临时数组最多2(n / k - 1)属于与查询间隔部分重叠的块的元素。对它们进行排序。使用this question的答案之一从已排序的临时数组和完全重叠的块的并集中选择所请求的排名的元素,在时间O(k log(n / k)^ 2)。理论上k的最佳设置是(n / log(n))^ 0.5。使用复杂的算法可以削减另一个log(n)^ 0.5。弗雷德里克森和约翰逊。

答案 2 :(得分:1)

执行存储桶排序的修改:创建一个包含所需范围内的数字的存储桶,然后仅对此存储桶进行排序并找到第k个最小值。

答案 3 :(得分:0)

该死的,这个解决方案无法更新元素,但至少找到了第k个元素,在这里你会得到一些想法,这样你就可以想到一些提供更新的解决方案。尝试基于指针的B树。

这是 O(n log n)空间和 O(q log ^ 2 n)时间复杂度。后来我用每个查询的 O(log n)解释了相同的内容。

所以,你需要做下一个:

1)在给定数组上创建“分段树”。

2)对于每个节点,您将存储整个数组,而不是存储一个数字。该数组的大小必须等于它的子数。该数组(如您所猜测的)必须包含底部节点(子节点或该节点中的数字)的值,但已排序。

3)要制作这样一个数组,你可以从它的两个儿子合并来自分段树的两个数组。但不仅如此,对于你刚刚制作的数组中的每个元素(通过合并),你需要记住数字在合并数组中插入之前的位置(基本上,它来自的数组,并在其中定位) 。以及指向未从同一数组插入的第一个下一个元素的指针。

4)使用此结构,您可以检查在某些段S中有多少数量低于给定值x的数字。您可以找到(使用二进制搜索)根节点数组中的第一个数字&gt; ; = x。然后,使用您所做的指针,您可以在O(1)中找到两个子数组(作为前一个节点的子节点的节点数组)的相同问题的结果。您停止为每个节点操作此降序,该节点表示整个段在给定段S内部或​​外部。时间复杂度为O(log n):O(log n)以查找第一个元素&gt; =对于S的所有分解段,x和O(log n)。

5)对解决方案进行二元搜索。

这是每个查询具有O(log ^ 2 n)的解决方案。但是你可以减少到 O(log n)

1)在完成上面所写的所有操作之前,您需要转换问题。您需要对所有数字进行排序并记住原始数组中每个数字的位置。现在这些位置代表您正在处理的阵列。调用该数组P.

如果查询段的边界是a和b。你需要找到P中的第k个元素,它是a和b之间的值(而不是索引)。该元素表示原始数组中结果的索引。

2)要找到第k个元素,你会做一些复杂度为O(log n)的反向跟踪。您将要求索引0和(某些其他索引)之间的元素数量在a和b之间的值。

3)假设您知道某个段(0,h)的此类问题的答案。从最大的一个开始,为从h开始的树中的所有段获得相同类型问题的答案。只要当前答案(来自段(0,h))加上你最后得到的答案大于k,就继续得到那些答案。然后更新h。继续更新h,直到树中只有一个以h开头的段。那个h是你在你所说的问题中寻找的数字的索引。

要从树中获得某个段的问题的答案,您将花费恰好O(1)的时间。因为你已经知道它的父节点的答案,并且使用我在第一个算法中解释的指针,你可以得到O(1)中当前段的答案。