數據結構——線段樹

線段樹(一種二叉搜索樹)

在這裏插入圖片描述
樹形結構的特點讓它更方便查詢搜索。
線段樹方便與對區間查詢,區間更新維護,這是因爲樹上的父親節點表示了一段區間。葉子結點纔是原始的元素。
在這裏插入圖片描述
如上圖所示:

利用線段樹解決問題的步驟如下(以模版題——給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;
}

謝謝觀看,喜歡就收藏唄!

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