线段树

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

 

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

 

线段树是一颗二叉搜索树,最终将一个区间[1, n]划分为一些[i, i+1]的单元区间,每个单元区间对应线段树中的一个叶子节点;每个节点用用变量count来记录覆盖该节点的线段条数。

那么线段树的处理对象是什么呢?答案就是一段较狭窄的区间,区间上的格点对应有限个固定的变量,就像是线性的数组用完全二叉排序树的形式表达。处理问题时,抽象出区间上的格点,也即是明确每个格点对应变量的含义!

 

线段树的定义:

设为T(a, b),参数ab表示顶点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(lr  :  integer) 

{[l,  r ]是待插入区间,lr都是原始顶点坐标}

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]是待删除区间,lr都是原始顶点坐标}

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]曾被插入且还未删除。如果建树后插入区间[25]而删除区间[34]是非法的。

 

线段树的动态维护:

线段树的作用主要体现在可以方便地动态维护其某些特征。例如,增加一个数据域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.MUpdate在每一次执行InsertDelete之后执行。定义如下:

 

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来动态维护测度后执行根结点的InsertDelete的复杂度仍为O(logN)

 

连续段数:(区间中互不相交的线段条数)

这里的连续段数指的是区间的并可以分解为多少个独立的区间。如[12][23][56]可以分解为两个区间[13][56],则连续段数为2。增加一个数据域Lines_Tree.line表示该结点的连续段数。Line的讨论比较复杂,内部结点不能简单地将左右孩子的Line相加。所以再增加Lines_Tree.lbdLines_Tree.rbd域。定义如下:

 

                1    左端点I被描述区间盖到

lbd  = 

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


 

                1     右端点J被描述区间盖到

rbd  = 

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

 

lbdrbd的实现:

 

               1  该结点count > 0;

 

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

 

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

 

 

               1  该结点count > 0;

 

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

 

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

 

 

有了lbdrbdLine域就可以定义了:


                 1  该结点count > 0;

 

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

 

                Leftchild.Line  +  Rightchild.Line  -  1
               
当该结点是内部结点且Count=0Leftchild.rbd = 1Rightchild.lbd = 1;

 

                Leftchild.Line  +  Rightchild.Line  
               
当该结点是内部结点且Count=0Leftchild.rbdRightchild.lbd不都为1。

 

据此,可以定义Update’动态地维护Line域。与Update相似,Update’也在每一次执行InsertDelete后执行。定义如下:

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.rbdRightchild.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

 

 

 

posted on 2010-05-26 08:20  android开发实例  阅读(255)  评论(0编辑  收藏  举报

导航