使用二叉索引树进行 RMQ 扩展

Posted

技术标签:

【中文标题】使用二叉索引树进行 RMQ 扩展【英文标题】:Using binary indexed trees for a RMQ extension 【发布时间】:2013-06-17 21:21:48 【问题描述】:

RMQ problem 可以这样扩展:

Given 是一个 n 整数数组 A

query(x, y):给定两个整数1≤xyn,求A[x], A[x+1], ... A[y]的最小值;

update(x, v): 给定一个整数 v 和 1 ≤ xnA[x] = v

这个问题可以在O(log n) 中使用segment trees 解决这两个操作。

这在纸面上是一种有效的解决方案,但在实践中,段树涉及大量开销,尤其是在递归实现时。

我知道有一种方法可以解决O(log^2 n) 中的一个(或两个,我不确定)操作的问题,使用二叉索引树(可以找到更多资源,但是@ 987654323@ 和 this 分别是 IMO 最简洁和详尽的)。对于适合内存的n 值,此解决方案在实践中更快,因为 BIT 的开销要少得多。

但是,我不知道如何使用 BIT 结构来执行给定的操作。例如,我只知道如何使用它来查询区间和。如何使用它找到最小值?

如果有帮助,我有其他人编写的代码可以满足我的要求,但我无法理解它。这是一段这样的代码:

int que( int l, int r ) 
    int p, q, m = 0;

    for( p=r-(r&-r); l<=r; r=p, p-=p&-p ) 
        q = ( p+1 >= l ) ? T[r] : (p=r-1) + 1;
        if( a[m] < a[q] )
            m = q;
    

    return m;


void upd( int x ) 
    int y, z;
    for( y = x; x <= N; x += x & -x )
        if( T[x] == y ) 
            z = que( x-(x&-x) + 1, x-1 );
            T[x] = (a[z] > a[x]) ? z : x;
        
        else
            if( a[ T[x] ] < a[ y ] )
                T[x] = y;

在上面的代码中,T 初始化为 0,a 是给定的数组,N 是它的大小(无论出于何种原因,它们都会从 1 开始索引),每次读取都会首先调用 upd价值。在upd 被调用之前a[x] = v 被执行。

此外,p &amp; -p 与某些 BIT 源中的 p ^ (p &amp; (p - 1)) 相同,索引从 1 开始,零元素初始化为无穷大。

谁能解释一下上面的工作原理或我如何用 BIT 解决给定的问题?

【问题讨论】:

for( y = x; x &lt;= N; x += x &amp; -x ) 这很深。顺便说一句,T[] 是什么? BTW2:我不确定这是否应该是for( y = x; x &lt; N; x += x &amp; -x ) BTW3:什么是N @wildplasser - 第一个 for 实际上对于 BIT 来说是相当标准的。 T 似乎是实际保存 BIT 信息的位置。至于N,也就是我的问题陈述中的n,我会编辑进去。 【参考方案1】:

我没有仔细看代码,但似乎与下面的方案大致一致:

1) 保留BIT的结构,即在数组上强加一个基于2的幂的树结构。

2) 在树的每个节点处,保持在该节点的任何后代中找到的最小值。

3) 给定一个任意范围,将指针放在范围的开始和结束处,并将它们向上移动直到它们相遇。如果您将一个指针向上移动并朝向另一个指针,那么您刚刚进入了一个节点,其中每个后代都是该范围的成员,因此请注意该节点处的该值。如果将指针向上移动并远离另一个指针,则刚刚加入的节点记录了从包括范围外的值中派生的最小值,并且您已经注意到范围内该节点下方的每个相关值,因此忽略该节点的值。

4) 一旦两个指针是同一个指针,范围内的最小值就是你注意到的任何节点中的最小值。

【讨论】:

【参考方案2】:

从上面的位摆弄,这就是我们所拥有的:

整数数据数组a 的普通位数组g 存储范围和。

g[k] = sum i = D(k) + 1 .. k  a[i]

其中D(k) 只是k,最低1 位设置为0。这里我们改为

T[k] = min i = D(k) + 1 .. k  a[i]

查询的工作方式与普通的 BIT 范围求和查询完全一样,只是在查询进行时取子范围的最小值而不是求和。对于a 中的 N 项,N 中有上限(log N)位,它决定了运行时间。

更新需要更多工作,因为 O(log N) 子范围最小值 - 即 g 的元素 - 受到更改的影响,并且每个都需要一个 O(log N) 查询来解决。这使得整体更新 O(log^2 n)。

在位摆弄级别,这是非常聪明的代码。语句x += x &amp; -x 清除x 中最低顺序的连续1 字符串,然后将下一个最高顺序零设置为1。这正是您“遍历”原始整数x 的BIT 所需要的。

【讨论】:

【参考方案3】:

分段树在实践中也是一种有效的解决方案。但是,您不会将它们实现为树。将n 舍入到下一个2 的幂,并使用大小为2*n 的数组rmqrmq 的最后一个 n 条目是 A。如果j &lt; n,那么rmq[j] = min(rmq[2*j], rmq[2*j+1])。您只需要以对数方式查看 rmq 的多个条目即可回答范围最小查询。当A 的条目被更新时,您只需要以对数方式更新rmq 的多个条目。

不过,我不明白你的代码,所以我不会评论它。

【讨论】:

当我说它们在实践中很慢时,这就是我所指的实现。 BIT 仍然对缓存更加友好,并且在实践中会更快。 @IVlad:在此处预取帮助。此外,您不需要使用 radix-2 树。您可以使用基数 B 树并调整 B 以适合您的缓存层次结构。 (也许 B=16 是合适的;它为您提供了 1/4 高度的树,而对于 ints,您每级只查看一个缓存行。) @IVlad:此外,BIT 解决了一个更简单的问题。它们为您提供前缀和(或产品或分钟),并且只有在您有取消法的情况下,您才能使用它们进行范围查询。我从未见过使用 BITs 代替分段树进行范围查询,但我也从未真正看过。

以上是关于使用二叉索引树进行 RMQ 扩展的主要内容,如果未能解决你的问题,请参考以下文章

Mysql的索引为什么要使用B+树?

为什么MySQL数据库索引选择使用B+树?

为什么MySQL数据库索引选择使用B+树?

为什么MySQL数据库索引选择使用B+树?

MySQL 索引与 B+ 树

二叉索引树