线段树(更新中)

来源:互联网 发布:java 静态代码块 编辑:程序博客网 时间:2024/06/03 17:01

作者:disappearedgod
文章出处:http://blog.csdn.net/disappearedgod/article/details/24425983
时间: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
线段树及其应用


0 0
原创粉丝点击