[算法]线段树

拖了好久才写的线段树......

大概听说它可能实在n年前,在我还是一个孩子的时候[/微笑]

恩大概我觉得有一丢丢丢像分块

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

一 概述

线段树,类似区间树,它在各个节点保存一条线段(数组中的一段子数组),主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。

线段树的每个节点表示一个区间,子节点则分别表示父节点的左右半区间,例如父亲的区间是[a,b],那么(c=(a+b)/2)左儿子的区间是[a,c],右儿子的区间是[c+1,b]。

二 从一个例子理解线段树

下面我们从一个经典的例子来了解线段树,问题描述如下:从数组arr[0...n-1]中查找某个数组某个区间内的最小值,其中数组大小固定,但是数组中的元素的值可以随时更新。

对这个问题一个简单的解法是:遍历数组区间找到最小值,时间复杂度是O(n),额外的空间复杂度O(1)。当数据量特别大,而查询操作很频繁的时候,耗时可能会不满足需求。

另一种解法:使用一个二维数组来保存提前计算好的区间[i,j]内的最小值,那么预处理时间为O(n^2),查询耗时O(1), 但是需要额外的O(n^2)空间,当数据量很大时,这个空间消耗是庞大的,而且当改变了数组中的某一个值时,更新二维数组中的最小值也很麻烦。

我们可以用线段树来解决这个问题:预处理耗时O(n),查询、更新操作O(logn),需要额外的空间O(n)。根据这个问题我们构造如下的二叉树

  • 叶子节点是原始组数arr中的元素
  • 非叶子节点代表它的所有子孙叶子节点所在区间的最小值

例如对于数组[2, 5, 1, 4, 9, 3]可以构造如下的二叉树(背景为白色表示叶子节点,非叶子节点的值是其对应数组区间内的最小值,例如根节点表示数组区间arr[0...5]内的最小值 是1):                                                                                                                           本文地址

由于线段树的父节点区间是平均分割到左右子树,因此线段树是完全二叉树,对于包含n个叶子节点的完全二叉树,它一定有n-1个非叶节点,总共2n-1个节点,因此存储线段是需要的空间复杂度是O(n)。那么线段树的操作:创建线段树、查询、节点更新 是如何运作的呢(以下所有代码都是针对求区间最小值问题)?

 1 struct SegTreeNode
 2 {
 3     int val;
 4 }segTree[10010];
 5 //建建建
 6 void build(int root,int arr[],int istart,iend)
 7 {
 8     if(istart==iend)
 9         segTree[root].val=arr[istart];
10     else
11     {
12         int mid=(istart+iend)/2;
13         build(root*2+1,arr,istart,mid);
14         build(root*2+2,arr,mid+1,iend);
15         segTree[root].val=min(segTree[root*2+1].val,segTree[root*2+2].val);
16     }
17 }
18 //单点查询
19 int query(int root,int nstart,int nend,int qstart,int qend)
20 {
21     if(nstart>qend || nend<qstart)
22         return INFINITE;
23     if(qstart<=nstart && qend>=nend)
24         return segTree[root].val;
25     int mid=(nstart+nend)/2;
26     return min(query(root*2+1,nstart,mid,qstart,qend),query(root*2+2,mid+1,nend,qstart,qend));
27 }
28 //单点更新
29 void update(int root,int nstart,int nend,int index,int addval)
30 {
31     if(nstart==nend)
32     {
33         if(nstart==index)segTree[index].val+=addval;
34         return;
35     }
36     int mid=(nstart+nend)/2;
37     if(index<=mid)update(root*2+1,nstart,mid,index,addval);
38     else update(root*2+2,mid+1,nend,index,addval);
39     segTree[root].val=min(segTree[2*root+1].val,segTree[2*root+2].val);
40 }

以及区间

  1 const int INFINITE = INT_MAX;
  2 const int MAXNUM = 1000;
  3 struct SegTreeNode
  4 {
  5     int val;
  6     int addMark;//延迟标记
  7 }segTree[MAXNUM];//定义线段树
  8
  9 /*
 10 功能:构建线段树
 11 root:当前线段树的根节点下标
 12 arr: 用来构造线段树的数组
 13 istart:数组的起始位置
 14 iend:数组的结束位置
 15 */
 16 void build(int root, int arr[], int istart, int iend)
 17 {
 18     segTree[root].addMark = 0;//----设置标延迟记域
 19     if(istart == iend)//叶子节点
 20         segTree[root].val = arr[istart];
 21     else
 22     {
 23         int mid = (istart + iend) / 2;
 24         build(root*2+1, arr, istart, mid);//递归构造左子树
 25         build(root*2+2, arr, mid+1, iend);//递归构造右子树
 26         //根据左右子树根节点的值,更新当前根节点的值
 27         segTree[root].val = min(segTree[root*2+1].val, segTree[root*2+2].val);
 28     }
 29 }
 30
 31 /*
 32 功能:当前节点的标志域向孩子节点传递
 33 root: 当前线段树的根节点下标
 34 */
 35 void pushDown(int root)
 36 {
 37     if(segTree[root].addMark != 0)
 38     {
 39         //设置左右孩子节点的标志域,因为孩子节点可能被多次延迟标记又没有向下传递
 40         //所以是 “+=”
 41         segTree[root*2+1].addMark += segTree[root].addMark;
 42         segTree[root*2+2].addMark += segTree[root].addMark;
 43         //根据标志域设置孩子节点的值。因为我们是求区间最小值,因此当区间内每个元
 44         //素加上一个值时,区间的最小值也加上这个值
 45         segTree[root*2+1].val += segTree[root].addMark;
 46         segTree[root*2+2].val += segTree[root].addMark;
 47         //传递后,当前节点标记域清空
 48         segTree[root].addMark = 0;
 49     }
 50 }
 51
 52 /*
 53 功能:线段树的区间查询
 54 root:当前线段树的根节点下标
 55 [nstart, nend]: 当前节点所表示的区间
 56 [qstart, qend]: 此次查询的区间
 57 */
 58 int query(int root, int nstart, int nend, int qstart, int qend)
 59 {
 60     //查询区间和当前节点区间没有交集
 61     if(qstart > nend || qend < nstart)
 62         return INFINITE;
 63     //当前节点区间包含在查询区间内
 64     if(qstart <= nstart && qend >= nend)
 65         return segTree[root].val;
 66     //分别从左右子树查询,返回两者查询结果的较小值
 67     pushDown(root); //----延迟标志域向下传递
 68     int mid = (nstart + nend) / 2;
 69     return min(query(root*2+1, nstart, mid, qstart, qend),
 70                query(root*2+2, mid + 1, nend, qstart, qend));
 71
 72 }
 73
 74 /*
 75 功能:更新线段树中某个区间内叶子节点的值
 76 root:当前线段树的根节点下标
 77 [nstart, nend]: 当前节点所表示的区间
 78 [ustart, uend]: 待更新的区间
 79 addVal: 更新的值(原来的值加上addVal)
 80 */
 81 void update(int root, int nstart, int nend, int ustart, int uend, int addVal)
 82 {
 83     //更新区间和当前节点区间没有交集
 84     if(ustart > nend || uend < nstart)
 85         return ;
 86     //当前节点区间包含在更新区间内
 87     if(ustart <= nstart && uend >= nend)
 88     {
 89         segTree[root].addMark += addVal;
 90         segTree[root].val += addVal;
 91         return ;
 92     }
 93     pushDown(root); //延迟标记向下传递
 94     //更新左右孩子节点
 95     int mid = (nstart + nend) / 2;
 96     update(root*2+1, nstart, mid, ustart, uend, addVal);
 97     update(root*2+2, mid+1, nend, ustart, uend, addVal);
 98     //根据左右子树的值回溯更新当前节点的值
 99     segTree[root].val = min(segTree[root*2+1].val, segTree[root*2+2].val);
100 }
时间: 2024-08-01 22:16:09

[算法]线段树的相关文章

[莫队算法 线段树 斐波那契 暴力] Codeforces 633H Fibonacci-ish II

题目大意:给出一个长度为n的数列a. 对于一个询问lj和rj.将a[lj]到a[rj]从小到大排序后并去重.设得到的新数列为b,长度为k,求F1*b1+F2*b2+F3*b3+...+Fk*bk.当中F为斐波那契数列.F1=F2=1.对每一个询问输出答案模m. 区间查询离线 用莫队算法 开棵权值线段树,然后用斐波那契的性质update F(n+m)=F(n+1)*F(m)+F(n)*F(m-1); #include<cstdio> #include<cstdlib> #includ

POJ 3368 Frequent values RMQ ST算法/线段树

                                                     Frequent values Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 15229   Accepted: 5550 Description You are given a sequence of n integers a1 , a2 , ... , an in non-decreasing order. In

[算法]线段树(IntervalTree)

转载请注明出处:http://www.cnblogs.com/StartoverX/p/4617963.html 线段树是一颗二叉搜索树,线段树将一个区间划分成一些单元区间,每一个区间对应线段树的一个叶节点.对于线段树的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b].因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度. 线段树的特征: 1.线段树的长度不超过logL(L是最长区间的长度). 2.线段树把区间上

HDU 4638 Group (莫队算法||线段树离散查询)

题目地址:HDU 4638 先写了一发莫队,莫队可以水过.很简单的莫队,不多说. 代码如下: #include <iostream> #include <string.h> #include <math.h> #include <queue> #include <algorithm> #include <stdlib.h> #include <map> #include <set> #include <s

笔试算法题(42):线段树(区间树,Interval Tree)

议题:线段树(Interval Tree) 分析: 线段树是一种二叉搜索树,将一个大区间划分成单元区间,每个单元区间对应一个叶子节点:内部节点对应部分区间,如对于一个内部节点[a, b]而言,其左子节点表示的区间为[a, (a+b)/2],其右子节点表示的区间为[1+(a+b)/2, b]: 对于区间长度为N的线段树,由于其单元节点都是[a, a]的叶子节点,所以其叶子节点数为N,并且整棵树为平衡二叉树,所以总节点数为2N-1,树的深度为log(N)+1: 插入操作:将一条线段[a, b]插入到

《ACM/ICPC 算法训练教程》读书笔记 之 数据结构(线段树详解)

依然延续第一篇读书笔记,这一篇是基于<ACM/ICPC 算法训练教程>上关于线段树的讲解的总结和修改(这本书在线段树这里Error非常多),但是总体来说这本书关于具体算法的讲解和案例都是不错的. 线段树简介 这是一种二叉搜索树,类似于区间树,是一种描述线段的树形数据结构,也是ACMer必学的一种数据结构,主要用于查询对一段数据的处理和存储查询,对时间度的优化也是较为明显的,优化后的时间复杂为O(logN).此外,线段树还可以拓展为点树,ZWK线段树等等,与此类似的还有树状数组等等. 例如:要将

nyoj 119士兵杀敌(三)(线段树区间最值查询,RMQ算法)

题目119 题目信息 执行结果 本题排行 讨论区 士兵杀敌(三) 时间限制:2000 ms  |  内存限制:65535 KB 难度:5 描写叙述 南将军统率着N个士兵,士兵分别编号为1~N,南将军常常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比較,计算出两个人的杀敌数差值.用这样的方法一方面能鼓励杀敌数高的人,还有一方面也算是批评杀敌数低的人,起到了非常好的效果. 所以,南将军常常问军师小工第i号士兵到第j号士兵中,杀敌数最高的人与杀敌数最低的人之间军功差值是多少. 如今,请你写一个程

经典算法题每日演练——第十二题 线段树

原文:经典算法题每日演练--第十二题 线段树 这一篇我们来看树状数组的加强版线段树,树状数组能玩的线段树一样可以玩,而且能玩的更好,他们在区间求和,最大,平均 等经典的RMQ问题上有着对数时间的优越表现. 一:线段树 线段树又称"区间树”,在每个节点上保存一个区间,当然区间的划分采用折半的思想,叶子节点只保存一个值,也叫单元节点,所 以最终的构造就是一个平衡的二叉树,拥有CURD的O(lgN)的时间. 从图中我们可以清楚的看到[0-10]被划分成线段的在树中的分布情况,针对区间[0-N],最多有

算法模板——线段树之Lazy标记

一.前言 前面我们已经知道线段树能够进行单点修改和区间查询操作(基本线段树).那么如果需要修改的是一个区间该怎么办呢?如果是暴力修改到叶子节点,复杂度即为\(O(nlog_n)\),显然是十分不优秀的.那么我们能不能向区间查询一样把复杂度降到\(O(log_n)\)呢? 二.算法流程 线段树肯定是兹瓷\(O(log_n)\)修改的,否则发明它有何用处?所以,我我们现在需要知道,如何快速进行区间修改操作.首先,我们回顾下终止节点 假定我要在这个图上修改区间[2,8],我只要修改掉图上所有的终止节点