如何使Fenwick树适应范围最小的查询

时间:2015-06-29 01:14:14

标签: algorithm fenwick-tree

Fenwick tree是一种数据结构,可以有效地回答主要查询:

  • 将元素添加到数组update(index, value)
  • 的特定索引
  • 查找从1到N find(n)
  • 的元素总和

这两项操作都在O(log(n))时间完成,我了解logic and implementation。实现一堆其他操作并不难,比如找到从N到M的总和。

我想了解如何使用FenQ树来调整RMQ。很明显,改变Fenwick树的前两个操作。但我没有弄清楚如何在从N到M的范围内找到最小值。

在寻找解决方案后,大多数人认为这是不可能的,少数人声称实际上可以做到(approach1approach2)。

第一种方法(用我的谷歌翻译写的俄语有0个解释,只有两个函数)依赖于三个数组(初始,左和右),因为我的测试对所有可能的测试用例都没有正常工作。

第二种方法只需要一个数组,并且基于O(log^2(n))中的声明运行,并且几乎没有解释为什么以及如何运作。我没试过测试它。

鉴于有争议的主张,我想知道是否有可能增加Fenwick树来回答update(index, value)findMin(from, to)

如果有可能,我会很高兴听到它是如何运作的。

2 个答案:

答案 0 :(得分:13)

是的,您可以将Fenwick树(二进制索引树)改为

  • 以O(log n)
  • 更新给定索引处的值
  • 以O(log n)(摊销)
  • 查询范围的最小值

我们需要2个Fenwick树和一个包含节点实际值的附加数组。

假设我们有以下数组:

index 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
value 1  0  2  1  1  3  0  4  2  5  2  2  3  1  0

我们挥动魔杖,出现以下树木:

Fenwick trees for problem example

请注意,在两个树中,每个节点表示该子树中所有节点的最小值。例如,在BIT2中,节点12的值为0,这是节点12,13,14,15的最小值。

查询

我们可以通过计算几个子树值和一个额外的实际节点值的最小值来有效地查询任何范围的最小值。例如,范围[2,7]的最小值可以通过取BIT2_Node2(表示节点2,3)和BIT1_Node7(表示节点7),BIT1_Node6(表示节点5,6)和REAL_4的最小值来确定 - 因此覆盖[2,7]中的所有节点。但是,我们如何知道我们想要查看哪些子树?

Query(int a, int b) {
  int val = infinity // always holds the known min value for our range

  // Start traversing the first tree, BIT1, from the beginning of range, a
  int i = a
  while (parentOf(i, BIT1) <= b) {
    val = min(val, BIT2[i]) // Note: traversing BIT1, yet looking up values in BIT2
    i = parentOf(i, BIT1)
  }

  // Start traversing the second tree, BIT2, from the end of range, b
  i = b
  while (parentOf(i, BIT2) >= a) {
    val = min(val, BIT1[i]) // Note: traversing BIT2, yet looking up values in BIT1
    i = parentOf(i, BIT2)
  }

  val = min(val, REAL[i]) // Explained below
  return val
}

可以在数学上证明两个遍历都将在同一节点中结束。该节点是我们范围的一部分,但它不是我们所看到的任何子树的一部分。想象一下我们范围的(唯一)最小值在该特殊节点中的情况。如果我们没有查找它,我们的算法会得到不正确的结果。这就是为什么我们必须对真实值数组进行一次查找。

为了帮助理解算法,我建议你用笔和笔来模拟它。论文,在上面的示例树中查找数据。例如,范围[4,14]的查询将返回值BIT2_4(代表4,5,6,7),BIT1_14(代表13,14),BIT1_12(代表9,10,11, 12)和REAL_8因此涵盖所有可能的值[4,14]。

更新

由于节点表示自身及其子节点的最小值,因此更改节点将影响其父节点,但不影响其子节点。因此,要更新树,我们从我们正在修改的节点开始,一直向上移动到虚构的根节点(0或N + 1,具体取决于哪个树)。

假设我们正在更新某个树中的某个节点:

  • 如果新值
  • 如果新值==旧值,我们可以停止,因为没有更多的更改级联向上
  • 如果新值&gt;事情变得有趣。

    • 如果旧值仍存在于该子树中的某个位置,我们就完成了
    • 如果没有,我们必须在real [node]和每个树[child_of_node]之间找到新的最小值,更改树[node]并向上移动

用于在 a 树中更新值为v的节点的伪代码:

while (node <= n+1) {
  if (v > tree[node]) {
    if (oldValue == tree[node]) {
      v = min(v, real[node])
      for-each child {
        v = min(v, tree[child])
      }
    } else break
  }
  if (v == tree[node]) break
  tree[node] = v
  node = parentOf(node, tree)
}

请注意,oldValue是我们替换的原始值,而当我们向上移动树时,v可能会重新分配多次。

二进制索引

在我的实验中,Range Minimum Queries的速度大约是Segment Tree实现速度的两倍,而且更新速度略快。其主要原因是使用超高效的按位运算在节点之间移动。他们得到了很好的解释here。 Segment Tree非常简单,所以想想性能优势真的值得吗?我的Fenwick RMQ的更新方法是40行,需要一段时间来调试。如果有人想要我的代码我可以把它放在github上。我还制作了一个粗暴的测试生成器,以确保一切正常。

我帮助理解了这个主题&amp;从芬兰算法社区实施它。图片来源为http://ioinformatics.org/oi/pdf/v9_2015_39_44.pdf,但他们对Fenwick的1994年论文表示赞同。

答案 1 :(得分:0)

Fenwick树结构适用于添加,因为添加是可逆的。它没有最低限度的工作,因为只要你的单元格被认为是两个或更多输入的最小值,你就可能丢失信息。

如果您愿意将存储需求加倍,则可以使用隐式构造的分段树(如二进制堆)支持RMQ。对于具有n个值的RMQ,将n值存储在数组的位置[n,2n)。位置[1,n)是聚合体,其中公式A(k)= min(A(2k),A(2k + 1))。位置2n是无限的哨兵。更新例程看起来应该是这样的。

def update(n, a, i, x):  # value[i] = x
    i += n
    a[i] = x
    # update the aggregates
    while i > 1:
        i //= 2
        a[i] = min(a[2*i], a[2*i+1])

这里的乘法和除法可以用效率变换代替。

RMQ伪代码更精致。这是另一个未经测试和未经优化的例程。

def rmq(n, a, i, j):  # min(value[i:j])
    i += n
    j += n
    x = inf
    while i < j:
        if i%2 == 0:
            i //= 2
        else:
            x = min(x, a[i])
            i = i//2 + 1
        if j%2 == 0:
            j //= 2
        else:
            x = min(x, a[j-1])
            j //= 2
    return x