#define MAXN 1000
int n; // 数组实际长度
int array[MAXN]; // 原始数组(下标从0开始)
int tree[MAXN]; // 树状数组(下标从1开始)
int p[MAXN]; // 前缀和数组(下标从1开始)
int lowbit(int x) {
return x & (-x);
}
- lowbit 函数:返回 x 的最低位 1 所代表的数值。例如,
lowbit(6)
返回 2(因为 6 的二进制是110
,最低位 1 对应的值是10
即 2)。这个函数是树状数组的核心工具,用于确定节点管辖范围和父子关系。 - tree 数组:树状数组的核心存储结构,
tree[i]
存储区间[i-lowbit(i)+1, i]
的元素和。
void init() {
for (int i = 1; i <= n; i++) {
tree[i] = array[i - 1]; // 注意array下标从0开始
for (int j = i - 1; j > i - lowbit(i); j -= lowbit(j)) {
tree[i] += tree[j];
}
}
}
- 初始化逻辑:
- 先将每个节点初始化为对应原始数组的值(
tree[i] = array[i-1]
)。 - 再累加前面的节点值:对于节点
i
,需要加上所有管辖范围与i
有重叠的前驱节点(即j > i - lowbit(i)
的节点)。
- 先将每个节点初始化为对应原始数组的值(
- 时间复杂度:O (n log n),适用于静态初始化。若需更高效的 O (n) 初始化,可改用差分法。
void update(int index, int value) {
while (index <= n) {
tree[index] += value;
index += lowbit(index);
}
}
- 功能:将原始数组中第
index
个元素增加value
,并更新树状数组。 - 更新路径:从当前节点开始,不断向上找到父节点(通过
index += lowbit(index)
),直到超出数组范围。 - 示例:若更新
index=3
,则更新路径为:3 → 4 → 8 → ...
,直到index > n
。
int q(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= lowbit(index);
}
return sum;
}
- 功能:计算原始数组前
index
个元素的和(即array[0] + array[1] + ... + array[index-1]
)。 - 查询路径:从当前节点开始,不断向左上方找到 “管辖前缀” 的节点(通过
index -= lowbit(index)
),直到index=0
。 - 示例:查询
index=7
的前缀和时,路径为:7 → 6 → 4 → 0
,累加tree[7] + tree[6] + tree[4]
。
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &array[i]);
}
init(); // 初始化树状数组
// 输出树状数组的内容
for (int i = 1; i <= n; i++) {
printf("%d ", tree[i]);
}
printf("\n");
// 计算并输出前缀和数组
for (int i = 1; i <= n; i++) {
p[i] = q(i);
printf("%d ", p[i]);
}
printf("\n");
return 0;
}
- 输入处理:读取数组长度
n
和n
个元素。 - 初始化树状数组:调用
init()
构建树状数组。 - 输出树状数组:直接打印
tree
数组,展示每个节点存储的值。 - 计算前缀和:通过
q()
函数计算每个位置的前缀和,并存储在p
数组中输出。
体会:
通过完成树状数组的代码实现,我对这一数据结构有了更深入的理解。最初理解 lowbit 函数的作用时较为抽象,但通过调试和示例推导,逐渐明白它如何划分区间、构建树状结构。初始化函数中,通过循环累加前驱节点值的逻辑,让我直观看到每个节点如何聚合子区间的和,这比单纯记忆公式更有助于掌握原理。
单点更新和前缀查询的递归式路径遍历(通过加减 lowbit)是树状数组的核心技巧,这种 “自底向上” 和 “自顶向下” 的操作模式,将 O (n) 的时间复杂度优化到 O (log n),体现了算法设计的精妙。在主函数测试中,通过输出 tree 数组和前缀和数组,验证了逻辑的正确性,尤其是看到 tree 数组中各节点存储的区间和与理论推导一致时,成就感较强。
不过,代码中初始化的 O (n log n) 复杂度仍有优化空间,后续可以尝试差分法实现 O (n) 初始化。此外,树状数组在逆序对、区间更新等场景的扩展应用,还需要进一步实践。这次实现不仅巩固了理论知识,也让我体会到算法与代码结合时,细节处理(如下标转换)的重要性,对提升逻辑思维和问题解决能力很有帮助。