线段树的建立

来源:互联网 发布:淘宝上aape旗舰店真假? 编辑:程序博客网 时间:2024/05/16 19:54

 

线段树的建立和维护
 
BY 竹子的叶子
 
一、线段树的定义
 
经常遇到一些与区间有关的题目,这类题目用通常的方法很麻烦,但是有一种特殊的数据结构——线段树(LineTree,不要说什么ST,我就喜欢叫他LineTree,怎么地?怎么地?可以很好地解决有关区间上的问题。
线段树是一棵二叉树,记为T(a,b),参数a,b表示区间[a,b],其中,b-a为区间的长度,记为L。
其根为:[a,b]
其左儿子为:[a,(a+b)div 2]
其右儿子为:[(a+b)div 2,b]
L=1,则为叶子节点。
 
除此之外,每个区间上还有一个专门记录覆盖次数的变量count,以及其他根据题目而变得其他计数变量。
线段树的定义如下:
type LineTree = ^Node;
     Node = record
       i,j,count:longint; //i,j表示区间[i,j],count表示计数
       lchild,rchile:LineTree;
     end;
 
二、线段树的维护
与堆一样,线段树也是动态数据结构,需要维护。维护无非就是建立、查找,当然,线段树中还需要有覆盖的过程;
 
1、线段树的建立
建立方法很简单,和二叉树的建立方法基本相同。
 
Procedure LineTreeCreate(Var Root:LineTree;a,b:longint); //建立一个线段树T(a,b)
Var lc,rc:LineTree
Begin
 New(root);
 Root^.i:=a; root^.j:=b; root^.count:=0;
 If b-a=1 then //递归的结束,即到达叶子节点
 Begin
    root^.lchild:=nil; root^.rchild:=nil;
 End
 Else begin
    LineTreeCreate(lc,a,(a+b)div 2); //建立左儿子线段树
    LineTreeCreate(rc,(a+b)div 2,b); //建立右儿子线段树
    Root^.lchild:=lc;
    Root^.rchild:=rc;
 End;
End;
 
于是,一个线段树就建立完毕了,空间复杂度约为2L
 
2、线段树的查找
查找方法很简单,就是找到一个区间然后返回他的count,问题是如果在一棵[1..10]的树中,查找[2..5]像这样没有确切与之对应的线段怎么办?
分解之,然后查找它们对应分解出来的。譬如[2..5]=[2..3]+[3..5]。事实上,最后的结果是[2..3]和[3..5]两个count的最小值(想想为什么?)
那么代码如下(接下来都是伪代码,模仿数据结构的):
 
FUNC. Find(root:LintTree; a,b:Int):Int;
VAR mid:Int;
 Mid = (root.a+root.b) div 2;
 if (root.a = a) and (root.b = b) then return(root.count)
 else
    if (a>root.b) or (b<root.a) then return(0)
    else if a>=mid then return(find(root.rc,a,b))
    else if b<=mid then return(find(root.lc,a,b))
    else return(min(find(root.lc,a,mid),find(root.rc,mid,b)));
 
3、线段数的插入
所谓的插入,就是指给一线段,然后“覆盖”在线段树上,我们的count域就是用来统计这个的。插入其实很简单,但如果考虑不周全就会出现这么一个问题:
insert(1,6); insert(6,7);
find(1,7)=??
很显然,如果我们不对他进行一点特殊的维护,线段树就会出错。仔细考虑之后,我们发现,其实每个父亲节点的Count的值很显然都是他两个儿子的Count的最小值(想想为什么?),并且一定是。
那么我们就有办法了,先写一个维护操作:
 
PROC. Update(root:LineTree);
 if (root.lc ≠ NIL) and (root.rc ≠ NIL) then
 【
    root.count = min(root.lc.Count,root.rc.count);
 
然后我们就可以开始插入了。
 
PROC. Insert(root:LineTree; a,b:Int);
VAR mid:Int;
 Mid = (root.a+root.b) div 2;
 if (a<=root.a) and (b>=root.b) then
 【
    root.count = root.count + 1;
    insert(root.lc,a,b);
    insert(root.rc,a,b); //如果覆盖了当前,那么他的儿子们也一定被覆盖
else
 if a>=mid then insert(root.rc,a,b)
 else if b<=mid then insert(root.lc,a,b)
 else
    insert(root.lc,a,b);
    insert(root.rc,a,b);
update(root);
 
4、线段树的删除
 
其实线段树的删除用的很少,但是有时有他确实不可缺少的。和一般的二叉树以及BST相比,线段数的删除有一个特殊的要求——被删除的部分,必须已经被插入(覆盖)过。
也就是说,如果你要remove(3,8)那么前提是insert(3,8)或者insert(2,8)、insert(3,9)等等,可是如何判断呢?先查找要删除的部分,如果返回值也就是要被删除的部分的Count>=1就可以删除了。
 
PROC remove(root:LineTree; a,b:Int);
VAR Mid:Int;
 Mid = (root.a+root.b) div 2; //其实这个Mid我一直想用他儿子或者直接放在他本身,免得每次都算
 if find(a,b) >= 1 then
 【
    if (root.a = a) and (root.b = b) then root.count = root.count – 1
    else
      if a>=mid then remove(root.rc,a,b)
      else if b<=mid then remove(root.lc,a,b)
      else
        remove(root.lc,a,mid);
        remove(root.rc,mid,b);
update(root);
 
5、线段树的其他操作
其实线段树是一种很灵活的数据结构,在她的内部我们可以添加很多有用的统计元素,譬如比较常用的用来计算当前节点中有多少段不同“颜色”(或者其他标记)的线段,最后一次被覆盖是什么状态?起始点和末尾点有什么状态?有哪些计数?这些都可以加入进去,但最最重要的是,线段树中要记得——区间的合并,就拿插入来说,insert(1,4)和insert(4,10)就相当于insert(1,10),但是如果不更新,不考虑到线段区间的合并,那么查询[1..10]的时候,就会出错。
 
通常情况下,区间的合并主要考虑当前节点儿子们的状态,他相邻的节点的状态(起始点和末尾点的状态)等等。只需要想到定义,就不会写错。
原创粉丝点击