线段树入门【转】
文章來自 : http://blog.csdn.net/x314542916/article/details/7837276
學習算法,自己收藏著。
線段樹的入門級 總結
??????線段樹是一種二叉搜索樹,與區間樹相似,它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。
??????對于線段樹中的每一個非葉子節點[a,b],它的左兒子表示的區間為[a,(a+b)/2],右兒子表示的區間為[(a+b)/2+1,b]。因此線段樹是平衡二叉樹,最后的子節點數目為N,即整個線段區間的長度。
??????使用線段樹可以快速的查找某一個節點在若干條線段中出現的次數,時間復雜度為O(logN)。而未優化的空間復雜度為2N,因此有時需要離散化讓空間壓縮。
【以下以 求區間最大值為例】
先看聲明:
- #include?<stdio.h>??
- #include?<math.h>??
- const?int?MAXNODE?=?2097152;??
- const?int?MAX?=?1000003;??
- struct?NODE{??
- ????int?value;????????//?結點對應區間的權值??
- ????int?left,right;???//?區間?[left,right]??
- }node[MAXNODE];??
- int?father[MAX];?????//?每個點(當區間長度為0時,對應一個點)對應的結構體數組下標??
【創建線段樹(初始化)】:
?????? 由于線段樹是用二叉樹結構儲存的,而且是近乎完全二叉樹的,所以在這里我使用了數組來代替鏈表上圖中區間上面的紅色數字表示了結構體數組中對應的下標。
在完全二叉樹中假如一個結點的序號(數組下標)為?I?,那么 (二叉樹基本關系)
I 的父親為 I/2,
I 的另一個兄弟為 I/2*2 或 I/2*2+1
I 的兩個孩子為 I*2 (左)?? I*2+1(右)
有了這樣的關系之后,我們便能很方便的寫出創建線段樹的代碼了。
?
[cpp] view plaincopy- void?BuildTree(int?i,int?left,int?right){?//?為區間[left,right]建立一個以i為祖先的線段樹,i為數組下標,我稱作結點序號??
- ????node[i].left?=?left;????//?寫入第i個結點中的?左區間??
- ????node[i].right?=?right;??//?寫入第i個結點中的?右區間??
- ????node[i].value?=?0;??????//?每個區間初始化為?0??
- ????if?(left?==?right){?//?當區間長度為?0?時,結束遞歸??
- ????????father[left]?=?i;?//?能知道某個點對應的序號,為了更新的時候從下往上一直到頂??
- ????????return;??
- ????}??
- ????//?該結點往?左孩子的方向?繼續建立線段樹,線段的劃分是二分思想,如果寫過二分查找的話這里很容易接受??
- ????//?這里將?區間[left,right]?一分為二了??
- ????BuildTree(i<<1,?left,?(int)floor(?(right+left)?/?2.0));??
- ????//?該結點往?右孩子的方向?繼續建立線段樹??
- ????BuildTree((i<<1)?+?1,?(int)floor(?(right+left)?/?2.0)?+?1,?right);??
- }??
【單點更新線段樹】:
?????? 由于我事先用 father[ ] 數組保存過 每單個結點 對應的下標了,因此我只需要知道第幾個點,就能知道這個點在結構體中的位置(即下標)了,這樣的話,根據之前已知的基本關系,就只需要直接一路更新上去即可。
?
[cpp] view plaincopy- void?UpdataTree(int?ri){?//?從下往上更新(注:這個點本身已經在函數外更新過了)??
- ??
- ????if?(ri?==?1)return;?//?向上已經找到了祖先(整個線段樹的祖先結點?對應的下標為1)??
- ????int?fi?=?ri?/?2;????????//?ri?的父結點??
- ????int?a?=?node[fi<<1].value;?//?該父結點的兩個孩子結點(左)??
- ????int?b?=?node[(fi<<1)+1].value;?//?右??
- ????node[fi].value?=?(a?>?b)?(a):(b);????//?更新這個父結點(從兩個孩子結點中挑個大的)??
- ????UpdataTree(ri/2);???????//?遞歸更新,由父結點往上找??
- }??
【查詢區間最大值】:
?????? 將一段區間按照建立的線段樹從上往下一直拆開,直到存在有完全重合的區間停止。對照圖例建立的樹,假如查詢區間為 [2,5]
紅色的區間為完全重合的區間,因為在這個具體問題中我們只需要比較這 三個區間的值 找出 最大值 即可。
- int?Max?=?-1<<20;??
- void?Query(int?i,int?l,int?r){?//?i為區間的序號(對應的區間是最大范圍的那個區間,也是第一個圖最頂端的區間,一般初始是?1?啦)??
- ????if?(node[i].left?==?l?&&?node[i].right?==?r){?//?找到了一個完全重合的區間??
- ????????Max?=?(Max?<?node[i].value)?node[i].value:(Max);??
- ????????return?;??
- ????}??
- ????i?=?i?<<?1;?//?get?the?left?child?of?the?tree?node??
- ????if?(l?<=?node[i].right){?//?左區間有涉及??
- ????????if?(r?<=?node[i].right)?//?全包含于左區間,則查詢區間形態不變??
- ????????????Query(i,?l,?r);??
- ????????else?//?半包含于左區間,則查詢區間拆分,左端點不變,右端點變為左孩子的右區間端點??
- ????????????Query(i,?l,?node[i].right);??
- ????}??
- ????i?+=?1;?//?right?child?of?the?tree??
- ????if?(r?>=?node[i].left){?//?右區間有涉及??
- ????????if?(l?>=?node[i].left)?//?全包含于右區間,則查詢區間形態不變??
- ????????????Query(i,?l,?r);??
- ????????else?//?半包含于左區間,則查詢區間拆分,與上同理??
- ????????????Query(i,?node[i].left,?r);??
- ????}??
- }?
轉載于:https://www.cnblogs.com/ediszhao/p/3983494.html
總結
- 上一篇: 利用Spring AOP与JAVA注解为
- 下一篇: 解决Apache CXF 不支持传递ja