截部分陈宏对用线段树解矩形并的轮廓(picture 问题的深入讨论)

Picture问题的深入讨论

基于对数据结构选择的进一步分析,我们来重新考虑一下Picture问题的数据结构的选择,即采用树形结构来描述一组超元线段的状态。

一、线段树

受到累计扫描过程的启发,一组超元线段属于轮廓的数目,它与跨越该组超元线段的矩形的纵向边位置关系密切。不妨把矩形的纵向边投影到Y轴上,这样就把矩形的纵向边看作闭区间,并称之为闭区间Q。我们以“线段树”的树形数据结构来描述闭区间Q。作为工具,先简单研究线段树的特点。

线段树是描述单个或若干区间并的树形结构,属于平衡树的一种。使用线段树要求知道所描述的区间端点可能取到的值。换句话说,设A[1..N]是从小到大排列的区间端点集合,对于任意一个待描述的闭区间P=[x,y],存在1≤i≤j≤N使得x=a[i]且y=a[j],这里i, j称为x,y的编号。可以看到,即使是实数坐标,在线段树中也只有整数含义。以下所说的区间[x, y]如无特殊说明,x、y均是整数,即原始区间顶点坐标的编号。

线段树是一棵二叉树,将数轴划分成一系列的初等区间[I, I+1] (I=1—N-1)。每个初等区间对应于线段树的一个叶结点。线段树的内部结点对应于形如[ I,  J ](J – I > 1)的一般区间。一般情况下,线段树的结点类型定义如下:

  Type

Lines_Tree = Object

  i, j   :  integer;       {结点表示的区间的顶点标号I, J}

  count :  integer;       {覆盖这一结点的区间数}

  leftchild, rightchild  :  ↑Lines_Tree;    {二叉树的两个子结点}

end

关于Lines_Tree的其它数据域与定义的运算将陆续添加。图8是一棵线段树,描述的区间端点可以有10种取值。其中记录着一个区间[3,6],它用红色的[3,5]及[5,6]的并采表示。图中红色结点的count域值为1,黑色结点的count域值为0。

 

直观地看,子结点就是父结点区间平均分成两部分。设L, R是父结点的区间端点,我们可以增加Lines_Tree.Build(l,r : integer)递归地定义线段树如下:

Procedure  Lines_tree.Build(l, r : integer)

1       I   ß  l            {左端点}

2       J   ß  r            {右端点}

3       Count  ß  0        {初始化}

4       If  r - l  >  1       {是否需要生成子结点,若r-l=1则是初等区间}

5         then   k  ß  (l  +  r)  /  2     {平均分为两部分}

6                new(leftchild)

7                leftchild↑.Build(l, k)    {建立左子树}

8                new(rightchild)

9                rightchild↑.Build(k, r)   {建立右子树}

10     else   leftchild ß  nil

11           rightchild  ß  nil

设根结点是Root,建树需要执行Root.Build。

由递归定义看出,线段树是一棵平衡树,高度为logN。建立整棵树需要的时间为O(N)。

以上着重说明了线段树的存储原理,我们还应建立线段树的基本运算。

线段树可以存储多个区间,所以支持区间插入运算Lines_Tree.Insert(l, r :integer),定义如下:

Procedure  Lines_Tree.Insert(l,r  : integer)  

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

1       if  (l  <= a[i])  and   (a[j] <=  r)               

2         then  count  ß  count  +  1            {盖满整个结点区间}

3         else  if  l< a[(i  +  j) div  2]  {是否能覆盖到左孩子结点区间}

4                 then leftchild↑.Insert(l, r)      {向左孩子插入}

5              if  r > a[(i +  j) div  2 ]   {是否能覆盖到右孩子结点区间}

6                 then rightchild↑.Insert(l,  r)    {向右孩子插入}

 

类似地,线段树支持区间的删除Lines_Tree.Delete(l, r : integer),定义如下:

Procedure  Lines_Tree.Delete(l,r  : integer)

{[l,  r]是待删除区间,l、r都是原始顶点坐标}

1       if  (l  <= a[i])  and   (a[j] <=  r)

2         then  count  ß  count  -  1            {盖满整个结点区间}

3         else  if  l< a[(i  +  j) div  2 ] {是否能覆盖到左孩子结点区间}

4                 then leftchild↑.Delete(l,  r)     {向左孩子删除}

5              if  r > a[(i +  j)  div  2 ]{是否能覆盖到右孩子结点区间}

6                 then rightchild↑.Delete(l, r)     {向右孩子删除}

执行Lines_Tree.Delete(l, r : integer) 的先决条件是区间[l, r]曾被插入且还未删除。如果建树后插入区间[2,5]而删除区间[3,4]是非法的。

通过分析插入与删除的路径,可知Lines_Tree.Insert与Lines_Tree.Delete的时间复杂度均为O(logN)。(详见[附录1])

由于线段树给每一个区间都分配了结点,利用线段树可以求区间并后的测度与区间并后的连续段数。

(一)、  测度

由于线段树结构递归定义,其测度也可以递归定义。增加数据域Lines_Tree.M表示以该结点为根的子树的测度。M取值如下:

 

 

        a[j] – a[i]   该结点Count>0

M  =   0          该结点为叶结点且Count=0

         Leftchild↑.M + Rightchild↑.M  该结点为内部结点且Count=0

 

据此,可以用Lines_Tree.UpData来动态地维护Lines_Tree.M。UpData在每一次执行Insert或Delete之后执行。定义如下:

Procedure  Lines_Tree.UpData

1       if  count  >  0

2         then  M  ß  a[j]  –  [i]      {盖满区间,测度为a[j] – a[i]}

3         else  if j  -  i  =  1         {是否叶结点}

4                 then M  ß  0       {该结点是叶结点}

5                 else M  ß  Leftchild↑.M  +  Rightchild↑.M
                                           {内部结点}

UpData的复杂度为O(1),则用UpData来动态维护测度后执行根结点的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

 

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

Procedure  Lines_Tree.UpData’

1       if  count  >  0          {是否盖满结点表示的区间}

2         then  lbd   ß 1

3              rbd  ß 1

4              Line ß  1

5         else  if   j -  i  =  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}

 

同时,由于增加了Line、M等几个数据域,在建树Lines_Tree.Build时要将新增的域初始化。

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

 

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  UpData;

  procedure  UpData’;

end

 

有了线段树这个工具,可以考虑利用树形结构来描述一组超元线段的状态。

 

 

 

 

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值