树结构的基本思想是分割。普通二叉搜索树是按对象来进行划分,效果往往和数据结构内对象有关;而线段树是根据关键码的可能范围来分的,这种技术叫做关键空间分解!

线段树的处理对象是线段(一般意义上的区间可以抽象成线段),它把线段组织成利于检索和统计的形式,本质上是线段的二叉搜索树!当然,线段树特殊点在与其中的线段可以分解和合并,另外,线段树操作的是整个区间,它的渐进时间效率不依赖于数据结构中的对象。

线段树是一颗二叉搜索树,最终将一个区间[1, n]划分为一些[i, i+1]的单元区间,每个单元区间对应线段树中的一个叶子节点;每个节点用用变量count来记录覆盖该节点的线段条数。
那么线段树的处理对象是什么呢?答案就是一段较狭窄的区间,区间上的格点对应有限个固定的变量,就像是线性的数组用完全二叉排序树的形式表达。处理问题时,抽象出区间上的格点,也即是明确每个格点对应变量的含义!

线段树的定义:
设为T(a, b),参数a和b表示顶点T为区间[a, b]。区间的长度b-a记为L。递归定义T[a, b]如下:
1)当区间长度L>1时,区间[a, (a+b) div 2]为T的左孩子,区间[(a+b) div 2, b]为T的右孩子;
2)如区间长度L=1时,T即为一个叶子顶点,表示区间[a, a+1];
区间[1, 10]的线段树如下:

定理:线段树把区间上的任意一条线段都分为不超过2lb(L)条线段。
线段树是平衡树,它的深度为lb(L),能在O(lb(L))的时间内完成一条线段的插入、删除、查找等工作。

线段树的节点类型定义如下:
Type
Lines_Tree = Object
B, E : integer; {结点表示的区间的顶点标号B, E}
count : integer; {覆盖这一结点区间的线段数}
leftchild, rightchild : ↑Lines_Tree; {二叉树的两个子结点}
end

建立线段树:
Procedure Lines_tree.Build(l, r : integer)
1 B ß l {左端点}
2 E ß r {右端点}
3 count ß 0 {初始化}
4 If r - l > 1 {是否需要生成子结点,若r-l=1则是初等区间}
5 then k ß (l + r) {平均分为两部分}
6 new(leftchild)
7 leftchild↑.Build(l, k) {建立左子树}
8 new(rightchild)
9 rightchild↑.Build(k, r) {建立右子树}
10 else leftchild ß nil
11 rightchild ß nil

将区间[l, r]插入线段树:
设线段树的根编号为v,如果[l, r]完全覆盖了v顶点代表的区间[a, b],那么显然该顶点上的基数(即覆盖线段树)加1;否则,如果[l, r ]不跨越区间中点,就只对左树或者右树上进行插入。如果[l, r]跨越区间中点,则在左树和右树上都要进行插入。注意观察插入的路径,一条待插入区间在某一个顶点上进行“跨越”,此后两棵子树上都要向下插入。
Procedure Lines_Tree.Insert(l,r : integer)
{[l, r ]是待插入区间,l、r都是原始顶点坐标}
1 if (l <= a[B]) and (a[E] <= r)
2 then count ß count + 1 {盖满整个结点区间}
3 else if l < a[(B + E) div 2] {是否能覆盖到左孩子结点区间}
4 then leftchild↑.Insert(l, r) {向左孩子插入}
5 if r > a[(B + E) div 2 ] {是否能覆盖到右孩子结点区间}
6 then rightchild↑.Insert(l, r) {向右孩子插入}

将区间[l, r]从线段树中删除:
从线段树中删除一个区间的方法与插入几乎完全类似。
Procedure Lines_Tree.Delete(l, r : integer)
{[l, r]是待删除区间,l、r都是原始顶点坐标}
1 if (l <= a[B]) and (a[E] <= r)
2 then count ß count - 1 {盖满整个结点区间}
3 else if l < a[(B + E) div 2 ] {是否能覆盖到左孩子结点区间}
4 then leftchild↑.Delete(l, r) {向左孩子删除}
5 if r > a[(B + E) div 2 ] {是否能覆盖到右孩子结点区间}
6 then rightchild↑.Delete(l, r) {向右孩子删除}
特别注意:执行Lines_Tree.Delete(l, r : integer) 的先决条件是区间[l, r]曾被插入且还未删除。如果建树后插入区间[2,5]而删除区间[3,4]是非法的。

线段树的动态维护:
线段树的作用主要体现在可以方便地动态维护其某些特征。例如,增加一个数据域Lines_Tree.M,存储以该结点为根的子树上线段并集的长度(即测度):

a[E] - a[B] 该结点Count>0
M = 0 该结点为叶结点且Count=0
Leftchild↑.M + Rightchild↑.M 该结点为内部结点且Count=0

只要每次插入或删除线段区间时,更新已访问顶点的M值,就可以在插入和删除的同时维持好M。求整个线段树的并集长度时,只要访问M[ROOT]的值就行了。
据此,可以用Lines_Tree.Update来动态地维护Lines_Tree.M。Update在每一次执行Insert或Delete之后执行。定义如下:

Procedure Lines_Tree.Update
1 if count > 0
2 then M ß a[E] - a[B] {盖满区间,测度为a[j] – a[i]}
3 else if E - B = 1 {是否叶结点}
4 then M ß 0 {该结点是叶结点}
5 else M ß Leftchild↑.M + Rightchild↑.M
{内部结点}Update的复杂度为O(1),则用Update来动态维护测度后执行根结点的Insert与Delete的复杂度仍为O(logN)。

连续段数:(区间中互不相交的线段条数)
这里的连续段数指的是区间的并可以分解为多少个独立的区间。如[1,2]∪[2,3]∪[5,6]可以分解为两个区间[1,3]与[5,6],则连续段数为2。增加一个数据域Lines_Tree.line表示该结点的连续段数。Line的讨论比较复杂,内部结点不能简单地将左右孩子的Line相加。所以再增加Lines_Tree.lbd与Lines_Tree.rbd域。定义如下:

1 左端点I被描述区间盖到
lbd =
0 左端点I不被描述区间盖到


1 右端点J被描述区间盖到
rbd =
0 右端点J不被描述区间盖到

lbd与rbd的实现:

1 该结点count > 0;

lbd = 0 该结点是叶结点且count = 0;

leftchild↑.lbd 该结点是内部结点且count=0。


1 该结点count > 0;

rbd = 0 该结点是叶结点且count = 0;

rightchild↑.rbd 该结点是内部结点且count=0。



有了lbd与rbd,Line域就可以定义了:

1 该结点count > 0;

Line = 0 该结点是叶结点且count = 0;

Leftchild↑.Line + Rightchild↑.Line - 1
当该结点是内部结点且Count=0,Leftchild↑.rbd = 1且Rightchild↑.lbd = 1;
Leftchild↑.Line + Rightchild↑.Line
当该结点是内部结点且Count=0,Leftchild↑.rbd与Rightchild↑.lbd不都为1。
据此,可以定义Update’动态地维护Line域。与Update相似,Update’也在每一次执行Insert或Delete后执行。定义如下:
Procedure Lines_Tree.Update’
1 if count > 0 {是否盖满结点表示的区间}
2 then lbd ß 1
3 rbd ß 1
4 Line ß 1
5 else if E - B = 1 {是否为叶结点}
6 then lbd ß 0 {进行到这一步,如果为叶结点,
count = 0}7 rbd ß 0
8 line ß 0
9 else line ß Leftchild↑.line + Rightchild↑.line -
Leftchild↑.rbd * Rightchild↑.lbd
{用乘法确定Leftchild↑.rbd与Rightchild↑.lbd是否同时为1}

至此,线段树构造完毕,完整的线段树定义如下:

Lines_Tree = object
i, j : integer;
count : integer;
line : integer;
lbd, rbd : byte;
m : integer;
leftchild,
rightchild : ↑Lines_tree;
procedure Build(l, r : integer);
procedure Insert(l, r : integer);
procedure Delete(l, r : integer);
procedure Update;
procedure Update’;
end