线段树初步
数列区间最大值(线段树)
https://www.acwing.com/problem/content/1272/
线段树跟树状数组十分类似。
树状数组:

线段树:

起始无论是线段树还是树状数组,本质上都是数组,都可以进行区间计算和单点更新。那么这两者有什么区别吗?
1.根据图像我们可以看出,树状数组严格按照2的幂次进行划分,所以有时会出现森林的情况,因此就无法通过某个结点到达所有的结点,而线段树我们可以看到有且只有一个总结点,而且是一个性质很好的树,与二分法的思想一致
2.从图像中我们还能看出在树状数组中,影响树状数组c[i]的是c[i]前面的元素,而影响线段树中tree[k]的是该结点的左右子树,是后面的元素,所以线段树tree[k]的值需要等左右子树构建完成才能确定,而且还要记录tree[k]的控制区间
构建线段树
在之前的分析中可以发现,线段树中的结点至少要记录两个要素:控制的区间以及区间的特征值(创建线段树的目的就是更方便的查找区间的元素),所以至少需要三个变量:区间的范围用两个变量l和r,特征值value,所以要构建一个结构体
struct Node
{
int l, r;
int value;
};
以上就是树结点的创建,那么如何创建树呢?
很简单,创建一个Node类型的数组就行了,那么问题来了,创建多大的合适呢?
首先要考虑父结点与左右结点的关系:
为了计算方便,假如父节点是tree[k],计左结点为tree[k * 2],右结点为tree[k * 2 + 1].这种排序方式符合完全二叉树的排列,能够很好的节省空间,而且方便找到左右结点
可以发现,假如构建的二叉树刚好填满最后一层,那么开2n个完全足够,但是假如最后一层没有完全填满,假设该树有m层可以计算出
所以最后一层所需的空间大于n小于2n,并且前m-1层所需的空间小于2n,为了不溢出,需要4n的空间
所以创建一个4n的Node类型的数组去记录线段树
Node tree[4*N];
现在构建结点k,假如k结点控制的区间为l到r,则有
void build_tree(int l, int r, int k)
{
tree[K].l = l, tree[K].r = r;
if(l == r)//叶子结点
{
tree[k].value = a[l];//a是原本的数组
return;
}
int mid = (l + r) >> 1;
build(l, mid, K << 1);
build(mid + 1, r, k << 1 | 1);
tree[k].value = {};//{}内是一个操作,得出的结果是这个区间的某个特征值,比如说区间的和,最值等
}
计算某个区间的特征值
假如说要计算区间[l, r]的特征值,来分析一下。
首先,肯定要从树根出发,那么就有区间[l, r]肯定比当前区间小或者相等,然后继续向下遍历此时会出现以下三种情况,设当前结点对应的区间为[L, R]:
1.区间[l, r]完全包含[L, R],此时就直接返回当前结点的特征值
2.区间[l, r]完全被[L, R]包含:
令mid=(L + R) >> 1;
假如mid >= r,就遍历左结点
假如mid < l,就遍历右结点
否则全部遍历
3.区间[l, r]与[L, R]有交集但不互相包含,同2
void search(int l, int r, int k)
{
if(tree[k].l >= l && tree[k].r <= r)
{
ans = {};//关于关键值的某个操作
return ;
}
int mid = (tree[k].l + tree[k].r) >> 1;
if(mid >= r)search(l, r, k << 1);
else(mid < l)search(l, r, k << 1 | 1);
else
{
search(l, r, k << 1);
search(l, r, k << 1 | 1);
}
}
至此,线段树的两个最基础的操作就完成了。

浙公网安备 33010602011771号