线段树(更新中)

作者:disappearedgod
时间:2014-4-24


前记

本想在查找 与 树中完成有关树的介绍,但是由于树的东西实在太多,而面试笔试也是一个重点,所以分出来写了个"数据结构-树",后来由于那篇单独介绍树的博客也太长,就暂时分开了成为了几个博客,在相关链接中能看到。
July 博客是被大家所知的,其原因是因为面试笔试题比较多。尽管他写的思路比较好,但是还有有很难以阅读的问题,也许是他把好阅读的方式放在了线下。
本文还是主要根据教材来进行书写《数据结构与算法》 Adam Drozdek的C++版本,代码还是用Java的较好一些。

正文

7.0 问题的提出

7.0.1 问题本身

有M个数排成一列,初始值全为0,然后做N次操作,每次我们可以进行如下操作:

(1)将指定区间的每个数加上一个值;

(2)将指定区间的所有数置成一个值;

(3)询问一个区间上的最小值、最大值、所有数的和。


7.0.2 问题的解释

这个问题本身不是一个牵强附会的问题,当进行统计的时候,有些统计方法是需要一段树来进行计算的,比如说是对时间序列的计算就像上面提出的问题一样

这个问题的一个普通的想法就是:

用一张线性表表示整个数列,每次执行前两个操作的时候,将对应区间里的数值逐一进行修改,执行第三个操作的时候,线性扫描询问区间,求出三个统计值,每次维护的时间复杂度O(m),整体的时间复杂度O(mn)。


线段树是一种可以直接维护所需要处理的区间的数据结构。





7.0.3 举一个例子-售票系统

       某次列车途经C个城市,城市编号依次为1到C,列车上共有S个座位,每一个售票申请包含三个参数,分别用O、D、N表示,O为起始站,D为目的地站,N为车票张数,售票系统对该售票申请作出受理或不受理的决定。只有在从O到D的区段内列车上都有N个或N个以上的空座位时该售票申请才被受理。1<=C<=60000,1<=S<=60000,1<=R<=60000,C为城市个数,S为列车上的座位数,R为所有售票申请总数。

输入:

4 6 4

1 4 2

1 3 2

2 4 3

1 2 3

输出:

YES

YES

NO

NO

7.0.4 线段树的应用场景

数据库的索引。

所以主要是需要了解对线段树的维护方法。


7.1 介绍 

线段树,也叫区间树,是一个完全二叉树,它在各个节点保存一条线段(即“子数组”),因而常用于解决数列维护问题,它基本能保证每个操作的复杂度为O(lgN)。

<span style="font-family:Microsoft YaHei;">struct Node
{
    int   left,right;  //区间左右值
    Node   *leftchild;
    Node   *rightchild;    
};</span>


7.2 定义

  1. 长度为1的线段称为元线段。
  2. 一棵树被称为是线段树,当且仅当这棵树满足如下条件:
    1. 该树是一棵二叉树;
    2. 树中的每一个节点都对应一条线段[a,b]
    3. 树中的节点是叶子节点,当且仅当它所代表的线段是元线段。
    4. 书中非叶子节点都有左右两棵子树,左子树树根对应线段[a,(a+b)/2],右子树树根对应线段是[(a+b)/2,b]


7.3 性质

性质1:长度范围为[1,L]的一棵线段树的深度不超过log2(L-1)+1;

性质2:线段树上的结点个数不超过2L个;

性质3:线段树把区间上的任意一条线段都分成不超过2log2L条线段。



7.4 维护

线段树上的参数通常有两种维护方法:

(1)一类参数表达了结点的性质,通常具有树型的递推性质,可以从下向上进行递推计算;(如sum,max,min)

(2)一类参数表达了子树的性质,维护的时候可以先打上标记,在需要进一步访问其子结点的时候从上向下传递。(如delta,en)


7.5 实现

<span style="font-family:Microsoft YaHei;">Node   *build(int   l ,  int r ) //建立二叉树
{
    Node   *root = new Node;
    root->left = l;
    root->right = r;     //设置结点区间
    root->leftchild = NULL;
    root->rightchild = NULL;

    if ( l +1< r )
    {
       int  mid = (r+l) >>1;
       root->leftchild = build ( l , mid ) ;
       root->rightchild = build ( mid  , r) ; 
    } 

    return    root; 
}</span>

插入和删除
<span style="font-family:Microsoft YaHei;">void  Insert(int  c, int d , Node  *root )
{
       if(c<= root->left&&d>= root->right) 
           root-> cover++;
       else 
       {
           if(c < (root->left+ root->right)/2 ) Insert (c,d, root->leftchild  );
           if(d > (root->left+ root->right)/2 ) Insert (c,d, root->rightchild  );
       }
}</span>

<span style="font-family:Microsoft YaHei;">void  Delete (int c , int  d , Node  *root )
{
       if(c<= root->left&&d>= root->right) 
           root-> cover= root-> cover-1;
       else 
       {
          if(c < (root->left+ root->right)/2 ) Delete ( c,d, root->leftchild  );
          if(d > (root->left+ root->right)/2 ) Delete ( c,d, root->rightchild );
       }
}</span>




7.6 评价


7.6.1 线段树与RMQ的比较


RMQ提供了一种高效的计算区间最值的方法。它的思想是将询问区间分解成两个最大的2的次幂的长度的区间并的形式。与线段树不同,这种区间分解是存在相交的分解。因此RMQ只能维护一些简单的信息,比如最值。
RMQ的优势:
(1)实现非常简单;
(2)效率比线段树更高;
线段树的优势:
(1)可以更好地维护动态的信息,而RMQ不推广到动态;
(2)可以维护更多的信息,而RMQ只能维护最值。

7.6.2 线段树与树状数组的比较


树状数组可以对单个元素进行高效的修改,并且可以高效的求部分和。

由于使用了位运算,因此树状数组的效率要优于线段树。

树状数组的空间开销也比线段树要小,但是树状数组的应用范围没有线段树广,能够转化使用树状数组的情况下尽量使用树状数组。


7.6.3 线段树与平衡树的比较


这两种数据结构解决了不同方面的问题,但是有的题目通过适当的建模,使用两种数据结构都能够加以解决。通常情况下,使用线段树的效率会优于平衡树。
但线段树有一点局限性:尽管能够在区间上进行修改操作,但不能插入或者删除区间。而这种插入与删除操作却是平衡树的基本操作之一。


相关链接

参考文章

线段树(segment tree)-实现

rmq


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值