学习笔记——线段树算法学习

这是从网上看到的两篇博客感觉挺好的就转过来,留作复习的材料。

转载来源:http://www.cnblogs.com/superbin/archive/2010/08/02/1790467.html

线段树(interval tree) 是把区间逐次二分得到的一树状结构,它反映了包括归并排序在内的很多分治算法的问题求解方式。

 

上图是一棵典型的线段树,它对区间[1,10]进行分割,直到单个点。这棵树的特点
是:
1. 每一层都是区间[a, b]的一个划分,记 L = b - a

2. 一共有log2L层
3. 给定一个点p,从根到叶子p上的所有区间都包含点p,且其他区间都不包含点p。
4. 给定一个区间[l; r],可以把它分解为不超过2log2 L条不相交线段的并。
其中第四点并不是很显然,图8.1显示了[2, 8]的分解方式,深灰色结点为分解后的
区间,浅灰色结点是递归分解过程中经过的结点。为了叙述方便,下面称树中的结点
所对应的区间为树中区间。

 

 

  从第3点和第4点可以得出结论:修改一个点只用修改log2 L个树中区间信息,而统计一个区间只需要累加2log2 L个树中区间的信息,且访问的总结点数是O(log L)的。两个操作都很容易实现。
  动态统计问题I 有一个包含n个元素的整数数组A,每次可以修改一个元素,也可以询问某一个区间[l; r]内所有元素的和。如何设计算法,使得修改和询问操作的时间复杂度尽量低?
  方法一 直接做, 则修改操作是O(1)的, 但询问需要进行累加, 时间复杂度为O(r ¡ l),最坏情况为O(n)。
  方法二 建立一棵线段树,每个树中区间记录该区间的元素和,则由刚才的结论,修改元素时只需要修改log2 L个树中区间的元素和,而统计时只需要累加2log2 L个树中区间的元素和,两个操作都是O(log n)的,比方法一好很多。
  动态统计问题II 有一个包含n个元素的整数数组A,每次可以同时给一个区间[l; r]内的数同时增加一个数d(d可以为负),也可以询问某一个数Ai的值。如何设计算法,使得修改和询问操作的时间复杂度尽量低?

  如何快速修改一个区间?修改一个树中区间[a; b]将影响到以[a; b]为根的整棵子树和它的所有祖先,所以如果沿用刚才的线段树定义,是不可能快速实现区间修改操作的。
  解决方法是借用数据结构中常用的\懒操作"的思想,只记录有哪些操作需要执行,而不去真正执行这些操作。换句话说,在需要给树中区间[l; r]的元素同时增
加d时,我们只记录\曾经有一条指令add(l, r, d)"就可以了。我们把记录的这个值称为树中区间的add值,则叶结点的元素值为它所有祖先的add值之和。根据前面的结论,每一条任意区间的修改指令可以分解成2log2 L个树中区间的修改指令,且修改操作具有叠加性,因此修改操作的时间复杂度为O(log n)。查询操作只
需要累加叶子的所有祖先,它也是O(log n)的。
  动态统计问题III 有一个包含n个元素的整数数组A,每次可以同时给一个区间[l; r]内的数同时增加一个数d(d可以为负),也可以询问一个区间[l; r]内所有元素的和。如何设计算法,使得修改和询问操作的时间复杂度尽量低?

  显然前两个问题都是此问题的特殊情况,因此这个问题比前两个问题难度更大。注意到上一个问题线段树只能提供叶结点的真正元素和,因为对于任何一个树中区间[l; r]来说,影响它的指令所修改的区间不仅包括它的所有祖先,也包括它的所有后代。所以[l; r]内的所有元素和应该等于[l; r]的所有祖先的add值加上[l; r]所有后代的add值。

  但后代有很多,直接累加的时间开销过大。这里需要再一次利用线段的树的区间相加性质,记录一个附加值sum_of_add,表示以[l; r]为根的子树上所有树中区间的add值之和。
  修改操作把区间分解为树中区间,修改它们的add值,并且要顺便修改它们父亲的sum_off_add值。由于区间分解过程访问的总结点是O(log L)的,因此修改操作是O(log n)的。
  查询操作把区间分解为树中区间,并统计它们所有祖先的add值之和,再与这些树中区间本身的sum_off_add相加即可。

 

文章出自《算法艺术与信息学竞赛》 ---- 作者:刘汝佳


转载来源:http://www.cnblogs.com/shuaiwhu/archive/2012/04/22/2464583.html

线段树(segment tree)

线段树在一些acm题目中经常见到,这种数据结构主要应用在计算几何和地理信息系统中。下图就为一个线段树:

(PS:可能你见过线段树的不同表示方式,但是都大同小异,根据自己的需要来建就行。)

1.线段树基本性质和操作

线段树是一棵二叉树,记为T(a, b),参数a,b表示区间[a,b],其中b-a称为区间的长度,记为L。

线段树T(a,b)也可递归定义为:

若L>1 :  [a, (a+b) div 2]为 T的左儿子;

             [(a+b) div 2,b]为T 的右儿子。 

若L=1 : T为叶子节点。

 

线段树中的结点一般采取如下数据结构:

struct Node
{
    int   left,right;  //区间左右值
    Node   *leftchild;
    Node   *rightchild;    
};

线段树的建立:

复制代码
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; 
}
复制代码

线段树中的线段插入和删除

增加一个cover的域来计算一条线段被覆盖的次数,因此在建立二叉树的时候应顺便把cover置0。

插入一条线段[c,d]:

复制代码
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  );
       }
} 
复制代码

 

删除一条线段[c,d]:

复制代码
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 );
       }
} 
复制代码

2.线段树的运用

线段树的每个节点上往往都增加了一些其他的域。在这些域中保存了某种动态维护的信息,视不同情况而定。这些域使得线段树具有极大的灵活性,可以适应不同的需求。

例一:

桌子上零散地放着若干个盒子,桌子的后方是一堵墙。如图所示。现在从桌子的前方射来一束平行光, 把盒子的影子投射到了墙上。问影子的总宽度是多少?

这道题目是一个经典的模型。在这里,我们略去某些处理的步骤,直接分析重点问题,可以把题目抽象地描述如下:x轴上有若干条线段,求线段覆盖的总长度,即S1+S2的长度。

 

2.1最直接的做法:

设线段坐标范围为[min,max]。使用一个下标范围为[min,max-1]的一维数组,其中数组的第i个元素表示[i,i+1]的区间。数组元素初始化全部为0。对于每一条区间为[a,b]的线段,将[a,b]内所有对应的数组元素均设为1。最后统计数组中1的个数即可。

初始     0   0  0  0  0
[12]   1   0  0  0  0
[35]   1   0  1  1  0
[46]   1   0  1  1  1
[56]   1   0  1  1  1

其缺点是时间复杂度决定于下标范围的平方,当下标范围很大时([0,10000]),此方法效率太低。

2.2离散化的做法:

基本思想:先把所有端点坐标从小到大排序,将坐标值与其序号一一对应。这样便可以将原先的坐标值转化为序号后,对其应用前一种算法,再将最后结果转化回来得解。该方法对于线段数相对较少的情况有效。

示例:

[10000,22000]   [30300,55000]   [44000,60000]   [55000,60000]

排序得10000,22000,30300,44000,55000,60000

对应得1, 2, 3, 4, 5, 6

然后是 [1,2]     [3,5]    [4,6]    [5,6]

初始     0   0  0  0  0
[12]   1   0  0  0  0
[35]   1   0  1  1  0
[46]   1   0  1  1  1
[56]   1   0  1  1  1

10000,22000,30300,44000,55000,60000

1,       2,        3,       4,       5,       6

(22000-10000)+(60000-30300)=41700

 

此方法的时间复杂度决定于线段数的平方,对于线段数较多的情况此方法效率太低。

2.3使用线段树的做法:

给线段树每个节点增加一个域cover。cover=1表示该结点所对应的区间被完全覆盖,cover=0表示该结点所对应的区间未被完全覆盖。

如下图的线段树,添加线段[1,2][3,5][4,6]

插入算法:

复制代码
void   Insert(Node  *root , int  a , int  b)
{
    int m;
    if( root ->cover == 0) 
    { 
        
        m = (root->left+ root->right)/2 ;
        if (a == root->left && b == root->right) 
            root ->cover =1;
        else if (b <= m)  Insert(root->leftchild , a, b);
        else if (a >= m)  Insert(root->rightchild , a, b);
        else 
        {    
                Insert(root->leftchild ,a, m);
                Insert(root->rightchild , m, b);
        }
    }
}
复制代码

统计算法:

复制代码
int  Count(Node *root)
{
    int  m,n;
    if (root->cover == 1)
            return   (root-> right - root-> left);
    else if (root-> right - root-> left== 1 )return 0;
    m= Count(root->leftchild);
     n= Count(root->rightchild);
    return m+n;
}
复制代码

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于 jQuery 的事件,我可以和你分享一些学习笔记。 1. 绑定事件 在 jQuery 中,我们可以通过以下方式来绑定事件: ``` $(selector).event(function(){ // 事件处理程序 }) ``` 其中,`selector` 表示要绑定事件的元素,`event` 表示要绑定的事件类型,比如 `click`、`mouseover` 等等。事件处理程序则是在事件触发时要执行的代码块。 2. 多个事件绑定 我们可以通过 `on()` 方法来同时绑定多个事件: ``` $(selector).on({ event1: function(){ // 事件处理程序1 }, event2: function(){ // 事件处理程序2 } }) ``` 这样,当 `event1` 或 `event2` 中任意一个事件触发时,对应的处理程序都会被执行。 3. 解除事件 如果需要解除某个元素的事件处理程序,可以使用 `off()` 方法: ``` $(selector).off(event); ``` 其中,`event` 表示要解除的事件类型。如果不指定事件类型,则会解除该元素上所有的事件处理程序。 4. 事件委托 在 jQuery 中,我们可以使用事件委托来提高性能。事件委托是指将事件绑定到父元素上,而不是绑定到子元素上,然后通过事件冒泡来判断是哪个子元素触发了该事件。这样,当子元素数量较多时,只需要绑定一次事件,就可以监听到所有子元素的事件。 ``` $(selector).on(event, childSelector, function(){ // 事件处理程序 }) ``` 其中,`selector` 表示父元素,`event` 表示要绑定的事件类型,`childSelector` 表示要委托的子元素的选择器,事件处理程序则是在子元素触发事件时要执行的代码块。 以上是 jQuery 中事件的一些基本操作,希望对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值