继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

『线段树 Segment Tree』

开心每一天1111
关注TA
已关注
手记 507
粉丝 48
获赞 218

<更新提示>

<第一次更新>


<正文>

线段树 Segment Tree

今天来讲一下经典的线段树。

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

简单的说,线段树是一种基于分治思想的数据结构,用来维护序列的区间特殊值,相对于树状数组,线段树可以做到更加通用,解决更多的区间问题。

性质

  • 1.线段树的每一个节点都代表了一个区间

  • 2.线段树是一棵二叉树,具有唯一的根节点,其中,根节点代表的是整个区间[1,n][1,n]

  • 3.线段树的每一个叶节点代表的是长度为11的元区间[x,x][x,x]

  • 4.对于每一个节点[l,r][l,r],它的左儿子被定义为[l,mid][l,mid],右儿子被定义为[mid+1,r][mid+1,r]

如图,这就是一棵维护了区间[1,10][1,10]的线段树。

20180819213931568.png

我们还可以发现,线段树层数为log2nlog2n层,除去最后一层,线段树是一棵完全二叉树。

建树 (build)

我们来考虑一下如何储存并建立一棵线段树。

由于线段树是二叉树,所以我们可以直接用数组存储结点的编号,即对于节点xx储存在a[p]a[p]处,我们令xx的左儿子储存在a[p∗2]a[p∗2]处,右儿子储存在a[p∗2+1]a[p∗2+1]处,这样就可以快速地找到节点之间的父子关系。

理想状态下,nn个叶节点的满二叉树有(∑2i=ni=02i)=2n−1(∑i=02i=n2i)=2n−1个节点,但由于最后一层至多还可能有2n2n个节点,所以数组空间要开到4n4n大小。

我们先来看一个维护区间最大值的例子。

对于线段树的每一个节点,我们可以额外的设置一个变量MaxMax代表该节点所代表区间中的最大值,显然有:Max(p)=max(Max(p∗2),Max(p∗2+1))Max(p)=max(Max(p∗2),Max(p∗2+1)),那么我们可以用如下方法建树。

Code:Code:

struct SegmentTree{
    int p,l,r,Max;    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define p(x) tree[x].p
    #define Max(x) tree[x].Max}tree[N*4];inline void build(int p,int l,int r)//对于节点p,代表的区间为[l,r]{
    l(p)=l,r(p)=r;//左右边界赋值
    if(l==r){Max(p)=0;return;}//如果为叶节点,直接赋值为权值
    int mid=(l+r)/2;    //递归构建子树
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    Max(p)=max(Max(p*2),Max(p*2+1));//回溯更新最大值}

修改 (modify)

线段树支持节点的动态修改。

对于如 "将节点xx修改权值为vv" 的指令,线段树可以以自下向上的方式修改。具体地,可以从根节点作为入口进入,递归向下找到需要修改的节点,再在回溯过程中更新沿路祖先节点的最值信息。时间复杂度O(log2n)O(log2n)。

Code:Code:

inline void modify(int p,int x,int v){    if(l(p)==r(p))
    {
        Max(p)=v;        return;
    }    int mid=(l(p)+r(p))/2;    if(x<=mid)modify(p*2,x,v);    if(x>mid)modify(p*2+1,x,v);
    Max(p)=max(Max(p*2),Max(p*2+1)); 
}

查询 (query)

线段树还需要能够解决区间最值查询问题。

对于如 "查询区间[l,r][l,r]的最大值" 的指令,线段树可以递归查找得到最大值。具体地,从根节点开始,递归执行以下过程:

  • 1.若[l,r][l,r]完全覆盖了当前结点所代表的区间,返回当前结点区间中的最大值作为备选答案

  • 2.若左子节点与[l,r][l,r]有重合部分,递归访问左子节点

  • 3.若右子节点与[l,r][l,r]有重合部分,递归访问右子节点

可以证明,区间查询的时间复杂度至多为O(2log2n)O(2log2n)。

Code:Code:

inline int query(int p,int l,int r){    if(l<=l(p)&&r>=r(p))return Max(p);    int mid=(l(p)+r(p))/2;    int res=-INF;    if(l<=mid)res=max(res,query(p*2,l,r));    if(r>mid)res=max(res,query(p*2+1,l,r));    return res;
}

至此,线段树的基本模型已经构成,我们通过一道模板题展示一下代码。

Description

给定一个包含n个数的序列,初值全为0,现对这个序列有两种操作:
操作1:把 给定 第k1 个数改为k2;
操作2:查询 从第k1个数到第k2个数得最大值。(k1<=k2<=n)
所有的数都 <=100000

Input Format

第一行给定一个整数n,表示有n个操作。
以下接着n行,每行三个整数,表示一个操作。
第一个树表示操作序号,第二个数为k1,第三个数为k2

Output Format

若干行,查询一次,输出一次。

Sample Input

31 2 21 3 32 2 3

Sample Output

3

Code:Code:

#include<bits/stdc++.h>using namespace std;const int N=100000+200,INF=0x3f3f3f3f;int n;struct SegmentTree{
    int p,l,r,Max;    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define p(x) tree[x].p
    #define Max(x) tree[x].Max}tree[N*4];inline void build(int p,int l,int r){
    l(p)=l,r(p)=r;    if(l==r){Max(p)=0;return;}    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    Max(p)=max(Max(p*2),Max(p*2+1));
}inline void modify(int p,int x,int v){    if(l(p)==r(p))
    {
        Max(p)=v;        return;
    }    int mid=(l(p)+r(p))/2;    if(x<=mid)modify(p*2,x,v);    if(x>mid)modify(p*2+1,x,v);
    Max(p)=max(Max(p*2),Max(p*2+1)); 
}inline int query(int p,int l,int r){    if(l<=l(p)&&r>=r(p))return Max(p);    int mid=(l(p)+r(p))/2;    int res=-INF;    if(l<=mid)res=max(res,query(p*2,l,r));    if(r>mid)res=max(res,query(p*2+1,l,r));    return res;
}inline void input(void){    scanf("%d",&n);
    build(1,1,n);
}inline void solve(void){    for(int i=1;i<=n;i++)
    {        int index,k1,k2;        scanf("%d%d%d",&index,&k1,&k2);        if(index==1)modify(1,k1,k2);        else printf("%d\n",query(1,k1,k2)); 
    }
}int main(void){
    input();
    solve();    return 0;
}


<后记>

原文出处:https://www.cnblogs.com/Parsnip/p/10467629.html  

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP