前记
7.0 问题的提出
7.0.1 问题本身
有M个数排成一列,初始值全为0,然后做N次操作,每次我们可以进行如下操作:
(1)将指定区间的每个数加上一个值;
(2)将指定区间的所有数置成一个值;
(3)询问一个区间上的最小值、最大值、所有数的和。
7.0.2 问题的解释
这个问题本身不是一个牵强附会的问题,当进行统计的时候,有些统计方法是需要一段树来进行计算的,比如说是对时间序列的计算就像上面提出的问题一样。
这个问题的一个普通的想法就是:
用一张线性表表示整个数列,每次执行前两个操作的时候,将对应区间里的数值逐一进行修改,执行第三个操作的时候,线性扫描询问区间,求出三个统计值,每次维护的时间复杂度O(m),整体的时间复杂度O(mn)。
线段树是一种可以直接维护所需要处理的区间的数据结构。
7.0.3 举一个例子-售票系统
某次列车途经C个城市,城市编号依次为1到C,列车上共有S个座位,每一个售票申请包含三个参数,分别用O、D、N表示,O为起始站,D为目的地站,N为车票张数,售票系统对该售票申请作出受理或不受理的决定。只有在从O到D的区段内列车上都有N个或N个以上的空座位时该售票申请才被受理。1<=C<=60000,1<=S<=60000,1<=R<=60000,C为城市个数,S为列车上的座位数,R为所有售票申请总数。
输入:
4 6 4
1 4 2
1 3 2
2 4 3
1 2 3输出:
YES
YES
NO
NO7.0.4 线段树的应用场景
数据库的索引。
所以主要是需要了解对线段树的维护方法。
7.1 介绍
线段树,也叫区间树,是一个完全二叉树,它在各个节点保存一条线段(即“子数组”),因而常用于解决数列维护问题,它基本能保证每个操作的复杂度为O(lgN)。
<span style="font-family:Microsoft YaHei;">struct Node
{
int left,right; //区间左右值
Node *leftchild;
Node *rightchild;
};</span>
7.2 定义
- 长度为1的线段称为元线段。
- 一棵树被称为是线段树,当且仅当这棵树满足如下条件:
- 该树是一棵二叉树;
- 树中的每一个节点都对应一条线段[a,b]
- 树中的节点是叶子节点,当且仅当它所代表的线段是元线段。
- 书中非叶子节点都有左右两棵子树,左子树树根对应线段[a,(a+b)/2],右子树树根对应线段是[(a+b)/2,b]
7.3 性质
性质1:长度范围为[1,L]的一棵线段树的深度不超过log2(L-1)+1;
性质2:线段树上的结点个数不超过2L个;
性质3:线段树把区间上的任意一条线段都分成不超过2log2L条线段。
7.4 维护
线段树上的参数通常有两种维护方法:
(1)一类参数表达了结点的性质,通常具有树型的递推性质,可以从下向上进行递推计算;(如sum,max,min)
(2)一类参数表达了子树的性质,维护的时候可以先打上标记,在需要进一步访问其子结点的时候从上向下传递。(如delta,en)
7.5 实现
<span style="font-family:Microsoft YaHei;">Node *build(int l , int r ) //建立二叉树
{
Node *root = new Node;
root->left = l;
root->right = r; //设置结点区间
root->leftchild = NULL;
root->rightchild = NULL;
if ( l +1< r )
{
int mid = (r+l) >>1;
root->leftchild = build ( l , mid ) ;
root->rightchild = build ( mid , r) ;
}
return root;
}</span>
插入和删除
<span style="font-family:Microsoft YaHei;">void Insert(int c, int d , Node *root )
{
if(c<= root->left&&d>= root->right)
root-> cover++;
else
{
if(c < (root->left+ root->right)/2 ) Insert (c,d, root->leftchild );
if(d > (root->left+ root->right)/2 ) Insert (c,d, root->rightchild );
}
}</span>
<span style="font-family:Microsoft YaHei;">void Delete (int c , int d , Node *root )
{
if(c<= root->left&&d>= root->right)
root-> cover= root-> cover-1;
else
{
if(c < (root->left+ root->right)/2 ) Delete ( c,d, root->leftchild );
if(d > (root->left+ root->right)/2 ) Delete ( c,d, root->rightchild );
}
}</span>
7.6 评价
7.6.1 线段树与RMQ的比较
(1)实现非常简单;
(2)效率比线段树更高;
线段树的优势:
(1)可以更好地维护动态的信息,而RMQ不推广到动态;
(2)可以维护更多的信息,而RMQ只能维护最值。
7.6.2 线段树与树状数组的比较
树状数组可以对单个元素进行高效的修改,并且可以高效的求部分和。
由于使用了位运算,因此树状数组的效率要优于线段树。
树状数组的空间开销也比线段树要小,但是树状数组的应用范围没有线段树广,能够转化使用树状数组的情况下尽量使用树状数组。
7.6.3 线段树与平衡树的比较
这两种数据结构解决了不同方面的问题,但是有的题目通过适当的建模,使用两种数据结构都能够加以解决。通常情况下,使用线段树的效率会优于平衡树。
但线段树有一点局限性:尽管能够在区间上进行修改操作,但不能插入或者删除区间。而这种插入与删除操作却是平衡树的基本操作之一。