数据结构实现 7.1:线段树(C++版)
1. 概念及基本框架
线段树 是一种二叉树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。
如上图中,每个结点可以存一些这个区间内的元素的性质,比如:和、最大值、最小值……通过不同区间的组合,我们可以访问到特定的区间元素的性质。因为划分区间我们采用二分的方法,而且左边的元素数目大于等于右边的元素数目,所以线段树本质上也是一棵完全二叉树。
注:这里线段树的每个结点存的只是一个元素,即这个区间元素的性质。
这里给出线段树大体框架:
template <class T>
class SegmentTree{
public:
SegmentTree(T *arr, int len){
m_data = new T[len];
for (int i = 0; i < len; ++i){
m_data[i] = arr[i];
}
m_size = len;
m_tree = new T[4 * len];
m_treeSize = 0;
buildSegmentTree(0, 0, m_size - 1);
}
...
private:
T *m_data;
int m_size;
T *m_tree;
int m_treeSize;
MergerNew<T> m;
};
m_data 用来接收线段树中原来的 n 个元素。
m_size 表示线段树的原数据大小。
m_tree 用来存储线段树每个结点的数据。
m_treeSize 表示线段树结点的数目。
m 可以认为内部有一个函数包可供我们调用,后面会详细讲述。
同样,为了保护数据,这些变量都放在 private 区。
buildSegmentTree 是一个线段树的构建函数,下面会详细讲述。
注:构造函数中,我们发现线段树结点提供了 4n 个,这是为了防止越界。
接下来我们就对线段树的构建、查询以及一些其他基本操作用代码去实现。
2. 基本操作程序实现
2.1 构建操作
为了不断划分区间,我们需要的到完全二叉树中,一个结点左右子结点的索引,这一点很类似于 6.1 中 最大二叉堆 的做法。原理不再赘述,给出其实现函数:
template <class T>
class SegmentTree{
...
private:
//返回完全二叉树中,一个结点左子结点的索引
int leftChild(const int index) const {
return 2 * index + 1;
}
//返回完全二叉树中,一个结点右子结点的索引
int rightChild(const int index) const {
return 2 * (index + 1);
}
...
};
有了索引,我们就可以不断地进行区间划分,进而构建出一棵线段树。构建的实现函数如下:
template <class T>
class SegmentTree{
...
private:
...
//在treeIndex位置创建表示区间[left...right]的线段树
void buildSegmentTree(const int treeIndex,const int left,const int right){
m_treeSize++;
if (left == right){
m_tree[treeIndex] = m_data[left];
return;
}
int leftTreeIndex = leftChild(treeIndex);
int rightTreeIndex = rightChild(treeIndex)