我在互联网上阅读了2 {3 binary indexed tree(AKA Fenwick树)的教程,但我不明白它实际上做了什么以及BIT
背后的想法是什么。
我读过的教程是
请帮助我了解BIT
。
答案 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]
其中d
是k
的值,除最低位之外的所有位都设置为零。例如,如果k=8
,则为d=8
。如果k=14
,则d=2
等
注意没有明确的树。树是合乎逻辑的,如教程图片所示。唯一的存储是数组g
。
为什么这是个好主意?事实证明,要查找sum_{i = a..b} f[i]
,您只需要总结2 ceiling(log(b+a))
个g
元素。可以通过分析a
和b
的二进制1位来确定这些元素。详细信息显示在教程中。
最简单的示例:如果您需要sum_{i = 1..p} f[i]
,则构建一系列索引p_1
,p_2
,... p_n
其中p_1 = p
和{通过从p_(i+i)
中删除最低阶1位来形成{1}}。因此,p_i
比n
的二进制表示中的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]
图表来自互联网