引入:數組
咱們常常會遇到須要維護一個序列的問題,例如,給定一個整數序列,每次操做會修改某個位置或某個區間的值,或是詢問你序列中的某個區間內全部數的和。或許你可能回去暴力出奇跡或者使用前綴和,可是當數據很大時,時間複雜度明顯是受不了的。那麼,就須要引入一種時間複雜度相對較小的數據結構 ——線段樹數據結構
目錄優化
基礎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個區間
那麼在查詢過程當中,會有如下三種狀況
├ 當前區間與需查詢區間無交集,返回 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%