【數據結構】線段樹

今天來仔細地說一下線段樹

線段樹可以高效率地解決許許多多的區間操作

比如區間求和,把一個區間中所有的數加上常量k,區間求最大值最小值等等

定義

  • 線段樹是一個完全二叉樹

  • 它在各個節點保存一條線段(數組中的一段子數組)

  • 每個單元區間對應線段樹中的一個葉結點

  • 性質:父親的區間是[l,r],(m=(l+r)/2)左兒子的區間是[l,m],右兒子的區間是[m+1,r],線段樹需要的空間爲數組大小的四倍

基本操作

這裏舉的是區間加與區間求和的例子

先列出來
1. 建線段樹
2. 區間加
3. 區間查詢和


建樹

  • 首先,需要一個結構體,來保存一個節點
struct node
{
    int left, right;
    //表示這個節點能夠在數組中管轄的線段的範圍是從left到right

    int s;
    //表示這條線段的區間和

    node *ch[2];
    //節點的兩個孩子,ch[0]爲左孩子,ch[1]是右孩子
}pool[MAXN], *root;//pool是內存池,root是根節點
  • 這樣就能夠用node來表示線段樹的節點(但這還不夠,詳看區間加)
  • 接下來是建樹操作
void Build_Tree(node *r, int left, int right)
//遞歸建樹,指以r爲根節點建線段爲left至right的子樹
{
    r->left = left;
    r->right = right;
    //將r->left更新爲left,r->right更新爲right
    if(left == right)
    //如果遞歸到了葉子節點
    {
        r->s = a[left];
        //單個葉子節點的區間和就是數組中的那個值
        return ;
    }
    int mid = (left + right) / 2;//詳見性質
    node *lson = &pool[++cnt];//申請空間
    node *rson = &pool[++cnt];//申請空間
    r->ch[0] = lson;//讓r的左子指向lson
    r->ch[1] = rson;//讓r的右子指向rson
    Build_Tree(lson, left, mid);     //遞歸建樹,詳見性質
    Build_Tree(rson, mid + 1, right);//遞歸建樹,詳見性質
    r->s = r->ch[0]->s + r->ch[1]->s;//維護和
}

區間加

即給定一個區間left,right和一個需要加上的值
完成操作:讓left ~ right的所有數加上一個d

  • 暴力操作:一個一個點進行單點修改,複雜度O(n2logn)
  • 顯然複雜度不滿足要求
  • 所以,我們換一種方式
  • 在每一個節點中加入一個懶標記,表示這個整個區間每一個數需要加上這個懶標記
  • 比如說要給[1,4] 加上5 ,那麼就讓表示[1,4] 的區間的lazy (懶標記)加上5
  • 我們需要一個pushdown函數,把一個節點的懶標記轉換成區間和並且下發到自己的左右兩子

pushdown:

inline void Push(node *r)
{
    if(r->lazy == 0) return ; //沒有需要加的值直接return
    r->s += (r->right - r->left + 1) * r->lazy;
    //區間和加上區間的長度(right - left + 1)乘上每個數需要加上的值lazy
    if(r->ch[0]) r->ch[0]->lazy += r->lazy;
    if(r->ch[1]) r->ch[1]->lazy += r->lazy;
    //左右兩子的lazy加上自己的lazy
    r->lazy = 0;
}

修改函數:

void change(node *r, int left, int right, int d)
{
    //d是要加上的數
    if(r->left == left && r->right == right)
    //找到了要修改的區間
    {
        r->lazy += d;//把每個數要加的值lazy加上d
        return ;
    }
    Push(r);
    //發放lazy
    if(r->ch[0]->right >= right) change(r->ch[0], left, right, d);
    //如果左子的右端點比要查找的右端點大,則在左子中繼續修改
    else if(r->ch[1]->left <= left) change(r->ch[1], left, right, d);
    //如果右子的左端點比要查找的左端點小,則在右子中繼續修改
    {
          change(r->ch[0], left, r->ch[0]->right, d);
          change(r->ch[1], r->ch[1]->left, right, d);
          //否則把需要查找的區間砍成兩半,分別在左右端點分別修改
    }
    r->s = r->ch[0]->s + r->ch[1]->s
    //因爲修改了值,所以需要重新維護s
}

區間和

  • 給你一個區間[left,right] ,求i=leftrightai
  • 與區間修改相似,給一個代碼
int query(node *r, int left, int right)
{
    Push(r);//最開始Push,免得返回時再算一遍
    if(r->left == left && r->right == right) return r->s;
    if(r->ch[0]->right >= right) return query(r->ch[0], left, right);
    else if(r->ch[1]->left <= left) return query(r->ch[1], left, right);
    else
        return query(r->ch[0], left, r->ch[0]->right) + 
               query(r->ch[1], r->ch[1]->left, right);

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