线段树及其基本操作

下图是对线段[1,10)建立的一棵线段树基本结构

1.每个节点都是一个[a,b)的区间,根节点代表了整个所要处理的区间

2. 对于每个非叶节点[a,b),令mid = (a + b) / 2;则其左右儿子节点代表的区间为[a,mid),[mid,b)

3.二叉的组织结构

4. 线段树是一个平衡树,树的高度为logN

5. 线段树把区间上的任意一条长度为L的线段都分成不超过2logL条线段的并

6. 任两个结点要么是包含关系要么没有公共部分,不可能部分重叠,每层节点的区间并即为全体区间

存储方式

线段树是一个树形结构,其上的信息都是保存在树的节点中。用结构体的方式建立节点,一个节点的基本结构如下:

struct node

{

    int left,right,mid;

};

其中left 和 right 分别代表该节点所 表示线段的左右端点,即当前节点所表示的线段为 [left ,right) 。而 mid = (left + right) / 2 ,为当前线段的中点。

这只是基本结构,在具体解题中,需要在节点中添加其他数据域保存信息。                              

堆式存储的特点:

1. N的左儿子是 2N

2. N 的右儿子是 2N + 1

3. N 的父亲是 N / 1

线段树的建树操作

结构体数组保存线段树,根节点下标为1。对于非叶节点num,其左右子节点下标分别为2*num和2*num+1。

node seg_tree[3*MAXN];

//由线段树的性质可知,建树需要的空间大概是所需要处理的最长线段的两倍多,所以需要开3倍大小的数组

void make(int l,int r,int num)

{

    seg_tree[num].left = l;

    seg_tree[num].right = r;

    seg_tree[num].mid = (l +r)/2;

    if (l+1!= r) return;

   make(l,seg_tree[num].mid,2*num);

   make(seg_tree[num].mid,r,2*num+1);

}

线段树的插入操作

为了记录节点中的线段是否被完全覆盖过,需要在节点中添加一个数据域cover。若cover为1则表示此条线段已经被完全覆盖过,否则未被覆盖。

插入操作的代码

void insert(int l,int r,int num)

{

//l,r分别为插入当前节点线段的左右端点,num为节点在数组中的编号

   if (seg_tree[num].left ==l&& seg_tree[num].right == r)

   {

//若插入的线段完全覆盖当前所表示的线段

      seg_tree[num].cover = 1;

      return;

   }

   if (r <=seg_tree[num].mid)

//当前节点的左子节点所代表的线段完全包含插入的节点

      insert(l,r,2*num);

   elseif(l>=seg_tree[num].mid)

//当前节点的右子节点所代表的线段完全包含插入的节点

      insert(l,r,2*num+1);

   else {

//插入线段跨越当前节点所表示的中点

      insert(l,seg_tree[num].mid,2*num);

     insert(seg_tree[num].mid,r,2*num+1);

   }

}

线段树的删除操作

一样采用递归的方法对线段进行删除,如果当前节点所代表的线段未被覆盖,则递归进入此节点的左右子节点进行删除。否则要考虑两种情况。一是删除的线段完全覆盖当前节点所代表的线段,则将当前节点的cover值置0。应该递归的在当前节点的子树上所有节点删除线段。另一种情况是删除的线段未完全覆盖当前节点所代表的线段,通常采用的方法是,将当前节点的cover置0,并将其左右子节点的cover置1,然后递归的进入左右子节点进行删除。

删除操作的代码

void del(intl,int r,int num)

{

    if (seg_tree[num].left ==l&&seg_tree[num].right == r)

    {

        seg_tree[num].cover = 0;

        return;

    }

    if (seg_tree[num].cover)

    {

        seg_tree[num*2].cover = 1;

        seg_tree[num*2 + 1].cover = 1;

        seg_tree[num] = 0;

    }

    if (r <= seg_tree[num].mid)

        del(l,r,2*num);

    else if (l>=seg_tree[num].mid)

        del(l,r,2*num+1);

    else {

         del(l,seg_tree[num].mid,2*num);

         del(seg_tree[num].mid,r,2*num+1);

    }

}

 线段树的统计操作

对应不同的问题,线段树会统计不同的数据,比如线段覆盖的长度,线段覆盖连续区间的个数等等,其实现思路不尽相同。

一般来说,统计都是在区间中进行的,所以依然需要采用递归的方式进行统计。

int cal(int num)

{

   if (seg_tree[num].cover)

     return seg_tree[num].right -seg_tree[num].left + 1;

   if (seg_tree[num].left + 1 == seg_tree[num].right)

     return 0;

   return cal(2*num) + cal(2*num+1);

}

                                                                                                                                                                                            ——本文摘自陈宇老师将要出版的算法图书,转载请标明出处

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值