数据结构——线段树

线段树(一种二叉搜索树)

在这里插入图片描述
树形结构的特点让它更方便查询搜索。
线段树方便与对区间查询,区间更新维护,这是因为树上的父亲节点表示了一段区间。叶子结点才是原始的元素。
在这里插入图片描述
如上图所示:

利用线段树解决问题的步骤如下(以模版题——给N个数,求任意区间合为例子):

对于这个算法个人认为必需理解:1.在建树时怎样将树全部遍历,将节点处理成区间和。2.在维护区间时利用lazy标记将数据添加到树中,并且如何在需要时将节点的lazy标记压入下面的节点。3.在查询区间时如何在需要时将节点的lazy标记压入下面的节点。
以上所述是算法的精髓所在,通则达。

详解如下

//预处理数据和主函数,方便后续阅读代码。p[]数组存放录入的数据,tr[N<<2]存放节点的值和lazy标记。
const int N=1e5+5;
int p[N];
struct node{
    long long sum,lazy;
}tr[N<<2];
int main()
{
    int n,m,i,k,a,b,q;
    scanf("%d%d",&n,&m);                          //输入n个数字,m次处理
    for(i=1;i<=n;i++){
        scanf("%d",&p[i]);
    }
    build_tree(1,1,n);
    while(m--){
        scanf("%d",&q);
        if(q==1){                                              //1表示区间加上k值
            scanf("%d%d%d",&a,&b,&k);
            add(1,1,n,a,b,k);
        }
        else{                                               //2表示 查询a到b的和
            scanf("%d%d",&a,&b);
            printf("%lld\n",search_tree(1,1,n,a,b));
        }
    }
    return 0;
}

1.利用题目中的关系建树,利用DFS,递归遍历到树的每一个点,这里面主要涉及到递归的回溯,将所有点都遍历到;
代码如下:


    void update(int root)
{
    tr[root].sum=tr[root<<1].sum+tr[root<<1|1].sum;     //将左右儿子节点上加到父亲节点。这就是区间的含义。
}
void build_tree(int root, int l, int r)
{
    if(l==r){
        tr[root].sum=p[l] ;                  //满足条件(遍历到了叶子结点)
        return;
    }
    int mid=(l+r)>>1;
    build_tree(root<<1,l,mid);                    //分流,左儿子;
    build_tree(root<<1|1,mid+1,r);              //分流,右儿子;
    update(root);                                       //传递给父亲节点
}

要是没学过dfs 理解会比较但一张图就可以让你豁然开朗!
在这里插入图片描述

2.下面是对区间的维护,对区间的维护就赤裸裸地体现了线段树的优势所在,每次查询一次就只需要查询到满足左右区间的父亲节点位置即可 ,有了lazy标记这玩意儿就大大缩减了所需时间。
在这里插入图片描述
代码如下:

void pushdown(int root, int l, int r)      //pushdown函数使原节点的lazy值压入左右儿子
{
    if(!tr[root].lazy) return;            //一种剪枝吧,不错。
    int mid=(l+r)>>1;
    tr[root<<1].sum+=(tr[root].lazy*(mid-l+1));  //更新左儿子节点的和
    tr[root<<1|1].sum+=(tr[root].lazy*(r-mid));  //更新右儿子节点的和
    tr[root<<1].lazy+=tr[root].lazy;             //更新左儿子节点lazy标记
    tr[root<<1|1].lazy+=tr[root].lazy;            //更新右儿子节点lazy标记
    tr[root].lazy=0;                           //将父亲节点的lazy标记清零
}
void add(int root, int l, int r, int a, int b, int k)
{
    if(l==a&&r==b){            //找到了目标节点区域
        tr[root].lazy+=k;       //更新  
        tr[root].sum+=k*(l-r+1);  //更新
        return ;
    }
    pushdown(root,l,r);     // 每一次都要检查是否需要将父亲节点压入左右节点。
    int mid=(l+r)>>1;
    if(mid>=b){               //  需要到左区间
        add(root<<1,l,mid,a,b,k); 
    }
    else if(mid<a){        //  需要到右区间
        add(root<<1|1,mid+1,r,a,b,k);
    }
    else{               // 左右区间都需要去找;
        add(root<<1,l,mid,a,mid,k);
        add(root<<1|1,mid+1,r,mid+1,b,k);
    }
    update(root);     //同样需要将父亲节点更新;
}

3.查询区间和;(这个点和上面的区间维护相似,主要也是区间查询,不同之处就是要返回区间的值)
代码如下:

long long search_tree(int root, int l, int r, int a, int b)
{
    if(l==a&&r==b){
        return tr[root].sum;
    }
    pushdown(root,l,r);     // 每次检查是否需要将父亲节点压入子节点;
    int mid=(l+r)>>1;       
    if(mid>=b){
        return search_tree(root<<1,l,mid,a,b);   //返回查到的值,同样是递归实现;
    }
    else if(mid<a){
        return search_tree(root<<1|1,mid+1,r,a,b);
    }
    else{
        return search_tree(root<<1,l,mid,a,mid)+search_tree(root<<1|1,mid+1,r,mid+1,b);
    }
}

学习这个算法时主要是要将递归实现理解透彻,将lazy标记这一方法理解透彻,将线段树这三个字理解透彻,区间区间区间?????No can no bibi !! …<~ - ~>…

耐心出奇迹

代码太长建议分块理解为妙:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+5;
int p[N];
struct node{
    long long sum,lazy;
}tr[N<<2];
void update(int root)
{
    tr[root].sum=tr[root<<1].sum+tr[root<<1|1].sum;
}
void build_tree(int root, int l, int r)
{
    if(l==r){
        tr[root].sum=p[l];
        return;
    }
    int mid=(l+r)>>1;
    build_tree(root<<1,l,mid);
    build_tree(root<<1|1,mid+1,r);
    update(root);
}
void pushdown(int root, int l, int r)
{
    if(!tr[root].lazy) return;
    int mid=(l+r)>>1;
    tr[root<<1].sum+=(tr[root].lazy*(mid-l+1));
    tr[root<<1|1].sum+=(tr[root].lazy*(r-mid));
    tr[root<<1].lazy+=tr[root].lazy;
    tr[root<<1|1].lazy+=tr[root].lazy;
    tr[root].lazy=0;
}
void add(int root, int l, int r, int a, int b, int k)
{
    if(l==a&&r==b){
        tr[root].lazy+=k;
        tr[root].sum+=k*(l-r+1);
        return ;
    }
    pushdown(root,l,r);
    int mid=(l+r)>>1;
    if(mid>=b){
        add(root<<1,l,mid,a,b,k);
    }
    else if(mid<a){
        add(root<<1|1,mid+1,r,a,b,k);
    }
    else{
        add(root<<1,l,mid,a,mid,k);
        add(root<<1|1,mid+1,r,mid+1,b,k);
    }
    update(root);
}
long long search_tree(int root, int l, int r, int a, int b)
{
    if(l==a&&r==b){
        return tr[root].sum;
    }
    pushdown(root,l,r);
    int mid=(l+r)>>1;
    if(mid>=b){
        return search_tree(root<<1,l,mid,a,b);
    }
    else if(mid<a){
        return search_tree(root<<1|1,mid+1,r,a,b);
    }
    else{
        return search_tree(root<<1,l,mid,a,mid)+search_tree(root<<1|1,mid+1,r,mid+1,b);
    }
}
int main()
{
    int n,m,i,k,a,b,q;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++){
        scanf("%d",&p[i]);
    }
    build_tree(1,1,n);
    while(m--){
        scanf("%d",&q);
        if(q==1){
            scanf("%d%d%d",&a,&b,&k);
            add(1,1,n,a,b,k);
        }
        else{
            scanf("%d%d",&a,&b);
            printf("%lld\n",search_tree(1,1,n,a,b));
        }
    }
    return 0;
}

谢谢观看,喜欢就收藏呗!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章