对二进制索引树概念的理解较少

时间:2012-08-03 02:46:24

标签: algorithm data-structures language-agnostic tree

我在互联网上阅读了2 {3 binary indexed tree(AKA Fenwick树)的教程,但我不明白它实际上做了什么以及BIT背后的想法是什么。 我读过的教程是

请帮助我了解BIT

4 个答案:

答案 0 :(得分:7)

顶级编码器文章不太清楚。这是一个可以帮助你入门的重要创意。

BIT适合存储从整数到整数f[i]的密集地图,其中i >= 1并且您有兴趣检索地图域范围内的总和,即任意{sum_{i = a..b} f[i] a 1}}和b。如果你用C编码,那将是:

sum = 0; for (i = a; i <= b; i++) sum += f[i];

对于大多数f[i]>0来说,密集我的意思是i。如果您有稀疏映射,则其他数据结构更好。

BIT可让您加快此计算速度,以便总和的运行时间 - 而不是与b - a成正比 - 而是与log(b+a)成比例。插入新元素的时间与此类似。

为此,BIT会存储不同的地图g[k]而不是f[i]g的内容以巧妙的方式定义。

g[k] = sum_{i = k - d + 1 .. k} f[i]  

其中dk的值,除最低位之外的所有位都设置为零。例如,如果k=8,则为d=8。如果k=14,则d=2

注意没有明确的树。树是合乎逻辑的,如教程图片所示。唯一的存储是数组g

为什么这是个好主意?事实证明,要查找sum_{i = a..b} f[i],您只需要总结2 ceiling(log(b+a))g元素。可以通过分析ab的二进制1位来确定这些元素。详细信息显示在教程中。

最简单的示例:如果您需要sum_{i = 1..p} f[i],则构建一系列索引p_1p_2,... p_n其中p_1 = p和{通过从p_(i+i)中删除最低阶1位来形成{1}}。因此,p_in的二进制表示中的1的数量少一个。现在只需计算

p

如果你进行实验并稍微考虑一下(双关语),你会明白为什么会这样。

答案 1 :(得分:2)

二进制索引树也称为Fenwick树,我认为二进制索引树与Fenwick树相比鲜为人知,所以当我们找到二进制索引树时,我们找到的材料较少,但这只是我的感觉!

简单来说,Fenwick树(也称为二进制索引树)是一种维护元素序列的数据结构,能够计算O(logn)时间内任何范围的连续元素的累积和。更改任何单个元素的值也需要O(logn)时间。 该结构节省空间,因为它需要与n个元素的简单数组相同的存储量。

上面说明的一个实际例子可以在网上的各个地方找到,即

http://codeforces.com/blog/entry/619

http://michaelnielsen.org/polymath1/index.php?title=Updating_partial_sums_with_Fenwick_tree

网上还有很多例子......

答案 2 :(得分:1)

二进制索引树是一种允许通过其前缀检索值的数据结构。我对二进制索引树的理解是它们或多或少类似于尝试。例如,假设您有三个数字1323,1697和1642.您可以将数字存储在树中:

1-3-2-3  
 -6-9-7  
   -4-2

其中每个节点代表一个10s的位置。现在你可以查找任何数字,就像你可以在电话簿中查找一个名字,一个字母。在这里,每个节点都是10秒,但您可以选择不同的基础以使表示尽可能紧凑。例如,您可以使用base 8,在这种情况下,每个节点存储4位。

此数据结构允许您轻松添加数字。例如,假设您要添加数字#1(1323)和#3(1642)。然后你从表示每个数字的叶子开始向上工作,乘以基数的幂(这里是10):3 + 2,然后是(2 + 4)* 10,然后是(6 + 3)* 100,然后(1 + 1)* 1000。

答案 3 :(得分:0)

BIT是一件非常奇特的事情,了解它的关键是了解快速分段算法的工作原理。

快速分段算法的基本概念&amp;数据结构是&#39; skip&#39; something.Think关于数组a [1..n]的整数。现在你想要另一个数组C [1..n],in C [i]包含C [i],C [i-1]到C [k(i)]之和(k是某种神奇函数(OO))。

当你想得到[1..i]的总和时,你可以写:

    int get_sum(int i) {
        if (i == 0)
            return 0;
        return get_sum(k(i)-1)+C[i];
    }

理解这一点很简单吗?我们只使用C [i] = sum(a [k(i).. i])而不是通过整个段。当我们想要更改某个元素时,我们使用一个名为&#39;添加(pos,i)&#39;意味着在[pos]中添加i:

(k&#39;(x)是函数返回最小y> = x表示k(y)&lt; = x)

    void add(int pos, int i) {
        if (pos <= n) {
            C[pos] += i;
            add(k'(pos), i);
        }
    }

代码的核心也是“跳过”。很明显C[k'(pos)]是其控制段(C [k(i).. i]的最小元素)&#39;包含C [pos]。我们只更新与C [pos]绑定的元素,而不是处理所有元素。

现在问题变成:&#39;找出合适的k(x)&amp; k&#39;(x)完成代码&#39;。 k(x) = x-sqrt(n)是一个简单的函数,上面的操作是O(sqrt(n))。那么更好的选择是功能BIT(i)。

BIT(i)是计算第一个&#39; 1&#39;在i的二进制文件中,例如:

    (110100)2 --BIT--> (100)2
    (111)2    --BIT--> (1)2
    (100000)2 --BIT--> (100000)2

计算BIT的一种快速方法是使用二元运算符:

    BIT(x) = x & ((~x) + 1)

让我们假设x = (101100)2, ~x = (010011)2, (~x)+1 = (010100)2, x & ((~x) + 1) = (000100)2。它真的有用! (~x)+1使除BIT(x)之外的所有位都相反,并且&#39;&amp;&#39;清除无用的位。

因为-x =(~x)+1(看看负整数如何变成二进制)         BIT(x)= x&amp; (-x)

现在我们获得了强大的功能。我没有解释这个方法(你自己理解它们会更好):

    k(i) = i-BIT(i)+1
    k'(i) = i+BIT(i)

尝试使用k&amp; K&#39;看到上面的代码。

[图] [http://i.stack.imgur.com/j5z5e.png]

  

图表来自互联网