我又活了
关于线段树和树状数组(仅个人观点
一、引子(就复制的概念,看看就好
在算法与数据结构领域中,线段树(Segment Tree)和树状数组(Fenwick Tree)是两种经典的数据结构,它们被广泛应用于解决各种问题,如区间查询、区间更新、前缀和计算等。本文将分别介绍线段树和树状数组的基本概念、原理、实现以及应用,并比较两者的优缺点,以帮助读者更好地理解和运用这两种数据结构。
二、线段树(Segment Tree)
1. 概念
线段树是一种基于分治思想的树形数据结构,用于高效地处理区间查询和区间更新操作。它将待处理的区间划分成若干个小区间,并使用二叉树进行表示。每个节点代表一个区间,根节点表示整个区间,而叶子节点表示单个元素。线段树的每个节点保存着该区间的相关信息,例如区间和、最大值、最小值等。
2. 原理
线段树的构建过程是一个递归的过程,首先将整个区间分为两半,然后递归地构建左右子树,直到区间长度为1。线段树的查询和更新操作也是递归进行的,通过二叉树的性质可以高效地进行区间操作。
3. 实现
// 线段树的节点定义
struct SegmentTreeNode {
int start, end; // 区间[start, end]
int sum; // 区间和
SegmentTreeNode *left, *right; // 左右子节点指针
};
// 构建线段树
SegmentTreeNode* buildSegmentTree(vector<int>& nums, int start, int end) {
if (start > end) {
return nullptr;
}
SegmentTreeNode* root = new SegmentTreeNode(start, end);
if (start == end) {
root->sum = nums[start];
} else {
int mid = start + (end - start) / 2;
root->left = buildSegmentTree(nums, start, mid);
root->right = buildSegmentTree(nums, mid + 1, end);
root->sum = root->left->sum + root->right->sum;
}
return root;
}
// 查询区间和
int queryRangeSum(SegmentTreeNode* root, int start, int end) {
if (root->start == start && root->end == end) {
return root->sum;
}
int mid = root->start + (root->end - root->start) / 2;
if (end <= mid) {
return queryRangeSum(root->left, start, end);
} else if (start > mid) {
return queryRangeSum(root->right, start, end);
} else {
return queryRangeSum(root->left, start, mid) + queryRangeSum(root->right, mid + 1, end);
}
}
// 更新单个元素
void updateSingleElement(SegmentTreeNode* root, int index, int value) {
if (root->start == root->end) {
root->sum = value;
return;
}
int mid = root->start + (root->end - root->start) / 2;
if (index <= mid) {
updateSingleElement(root->left, index, value);
} else {
updateSingleElement(root->right, index, value);
}
root->sum = root->left->sum + root->right->sum;
}
4. 应用
线段树广泛应用于解决各种区间操作问题,如区间和查询、区间最值查询、区间更新等。典型的应用包括区间求和问题、区间最值问题、离散化等。
三、树状数组(Fenwick Tree)
1. 概念
树状数组是一种用于高效计算前缀和的数据结构,它通过巧妙地利用二进制表示的技巧,实现了较快的更新和查询操作。树状数组的核心思想是利用二进制的特性,通过修改数组元素的方式实现前缀和的快速计算。
2. 原理
树状数组的核心操作是更新和查询,其中更新操作是通过不断累加数组元素实现的,查询操作则是利用二进制位操作实现的。通过这种方式,树状数组可以高效地计算前缀和。
3. 实现
// 树状数组的更新操作
void updateFenwickTree(vector<int>& tree, int index, int value) {
int n = tree.size();
while (index < n) {
tree[index] += value;
index += index & (-index);
}
}
// 树状数组的前缀和查询操作
int queryPrefixSum(vector<int>& tree, int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= index & (-index);
}
return sum;
}
4. 应用
树状数组主要用于高效计算前缀和,常见的应用包括数组区间操作的统计、逆序对计算、频率计数等。
四、线段树与树状数组的比较
特性 | 线段树 | 树状数组 |
---|---|---|
存储结构 | 树形结构 | 数组结构 |
操作复杂度 | 构建O(n),更新O(logn),查询O(logn) | 更新O(logn),查询O(logn) |
食用の范围 | 区间操作 | 前缀和计算 |
空间复杂度 | O(n) | O(n) |
五、总结
线段树和树状数组是两种重要的数据结构,它们分别适用于不同的问题,并且在实际应用中发挥着重要作用。通过学习和理解这两种数据结构的原理、实现和应用,可以更好地解决相关问题,并且在算法竞赛和工程实践中发挥作用。
废话
奥抱歉 最近真的有一点忙 hhh,会更的会更的 放心hhh
参考资料
算法竞赛入门经典
为什么要用指针和
vector
啊(我也是根据我们sb老师的讲解来记得,多多益善((乐
别看,纯闲的