线段树初步

数列区间最大值(线段树)

https://www.acwing.com/problem/content/1272/

线段树跟树状数组十分类似。

树状数组:

![image-20210411000805624](file://C:/Users/19065/AppData/Roaming/Typora/typora-user-images/image-20210411000805624.png?lastModify=1620388564?lastModify=1620390468)

线段树:

![image-20210507195711038](file://C:/Users/19065/AppData/Roaming/Typora/typora-user-images/image-20210507195711038.png?lastModify=1620390468)

起始无论是线段树还是树状数组,本质上都是数组,都可以进行区间计算和单点更新。那么这两者有什么区别吗?

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层可以计算出

\[2^{m-2}<n<2^{m-1}<2*n \]

所以最后一层所需的空间大于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);
	}
}

至此,线段树的两个最基础的操作就完成了。

posted @ 2021-05-07 21:52  kanbujian55  阅读(63)  评论(0)    收藏  举报