線段樹

引入:數組

咱們常常會遇到須要維護一個序列的問題,例如,給定一個整數序列,每次操做會修改某個位置或某個區間的值,或是詢問你序列中的某個區間內全部數的和。或許你可能回去暴力出奇跡或者使用前綴和,可是當數據很大時,時間複雜度明顯是受不了的。那麼,就須要引入一種時間複雜度相對較小的數據結構 ——線段樹數據結構

 


目錄優化

  基礎ui

    ├ 關於線段樹spa

    ├ 建樹3d

    ├ 區間查詢code

    └ 單點修改blog

   進階
遞歸

    ├ 延遲標記ip

    │   區間修改 單點查詢

    區間修改 區間查詢

               標記下傳

               標記永久化


基礎篇

關於線段樹

  線段樹是一課二叉樹樹上的每個結點對應序列的一段區間,以下圖:

       

  不難發現,根節點對應的區間時[0,n-1],而每一個結點對應一個區間[l,r],且當l=r時,該結點爲葉結點,無左右兒子,不然令 mid = (l + r) / 2 ,則左兒子對應的區間爲[l,mid],右兒子爲[mid+1,r]。同時,也不難發現,最後一行有n個結點,倒數第二行有n/2個結點,倒數第三行有n/4個結點,以此即可以推出一個線段樹有n+n/2+n/4+...+1=2*n+1個結點,可是要注意,線段樹數組務必要開4倍。設線段樹的深度爲h,那麼h爲O(logn)級別,當咱們須要維護的序列長度爲2的整數次冪時,這個線段數爲滿二叉樹,不然最後一層可能不滿

 


建樹

  注:接下來的一系列操做都以求區間和爲例

  設序列a:5,3,7,9,6,4,1,2,咱們將其用線段樹構建出來,即

       

  對應的區間和如圖

  

  從圖中能夠看出除葉結點區間和爲它自己外,其餘節點的區間和均爲其左右兒子區間和之和。以此,能夠經過遞歸建樹,並在遞歸後對其區間和進行維護

 1 void build(int k,int l,int r){   //k爲結點編號,l爲區間左端點,r爲區間右端點 
 2     if (l == r){  //若是該點爲葉結點 
 3         sum[k] = a[l];  //區間和即爲自己 
 4         return;
 5     }
 6     int mid  = l + r >> 1; //取中間值,位運算優化常數 
 7     build(k << 1,l,mid); //構建左子樹 
 8     build(k << 1 | 1,mid + 1,r); //構建右子樹 
 9     sum[k] = sum[k << 1] + sum[k << 1 | 1]; //維護該區間區間和 
10 }

區間查詢

  若是咱們要查詢區間和[0,6],那麼咱們需訪問3個區間

  cha

   那麼在查詢過程當中,會有如下三種狀況  

    ├ 當前區間與需查詢區間無交集,返回 0

    ├ 當前區間被需查詢區間徹底包含,返回該結點對應區間和

    └ 當前區間與需查詢區間有交集,但不被徹底包含,遞歸其左右子樹進行查詢

1 int query(int k,int x,int y,int l,int r){ //k爲結點編號,x爲當前區間左端點,y爲當前區間右端點,
2                                           //l爲需查詢區間左端點,r爲需查詢區間右端點 
3     if (x > r || y < l) return 0; //若是無交集,返回0 
4     if (x >= l && y <= r) return sum[k]; //若是徹底包含,返回該結點區間和 
5     int res = 0,mid = x + y >> 1;
6     res = query(k << 1,x,mid,l,r); //遞歸左子樹 
7     res += query(k << 1 | 1,mid + 1,y,l,r); //遞歸右子樹 
8     return res;
9 }

 

 單點修改

  假設要將a1加2,那麼咱們要從新計算4個結點的值

  改前 

                                          ||

                     \/  

    改後

  那麼,在修改時能夠用遞歸的形式修改

  在遞歸中,可能會有如下幾種狀況

     當前區間不包括需修改區間,直接返回

     找到需修改的葉結點,修改,返回

     有包含,但不是葉結點,遞歸修改左右子樹

 1 void change(int k,int l,int r,int p,int v){
 2     if (l > p || r < p) return; //若該區間不包含需修改點,直接返回 
 3     if (l == r && l == p){ //若當前即爲需修改結點 
 4         sum[l] += v; //修改 
 5         return;
 6     }
 7     int mid = l + r >> 1;
 8     change(k << 1,l,mid,p,v);//遞歸修改左子樹 
 9     change(k << 1 | 1,mid + 1,r,p,v);//遞歸修改右子樹 
10     sum[k] = sum[k << 1] + sum[k << 1 | 1];//維護區間和 
11 } 

 


 

進階篇

延遲標記

  有時咱們須要解決的不僅是單點修改、區間詢問,而是區間修改、區間詢問,那麼單純使用以上的方法已經沒法解決問題了,由於咱們沒有辦法高效完成區間修改

   以區間內全部數同時加上一個值,以及單點詢問爲例進行引入

區間修改,單點查詢

  考慮在每一個結點上維護一個值addsum,表示這個結點所對應的區間內的全部數都加上了addsum


第一次發佈時間:2019.11.28  完成度:45%

相關文章
相關標籤/搜索