轉自:http://blog.csdn.net/liujian20150808/article/details/51137749node
1.線段樹的定義:web
線段樹是一種二叉搜索樹,與區間樹類似,它將一個區間劃分紅一些單元區間,每一個單元區間對應線段樹中的一個葉結點。ui
對於線段樹中的每個非葉子節點[a,b],它的左兒子表示的區間爲[a,(a+b)/2],右兒子表示的區間爲[(a+b)/2+1,b]。所以線段樹是平衡二叉樹,最後的子節點數目爲N,即整個線段區間的長度。this
舉例描述:url
所以有了以上對線段樹的定義,咱們能夠將區間[1,10]的線段樹結構描述出來:spa

圖片來自於百度百科.net
有了線段[1,10]的線段樹結構,咱們能夠發現,每一個葉節點對應區間範圍內的端點[a,a](1<=a<=10)orm
2.構造線段樹blog
顯然,當咱們將線段樹定義清楚以後,那麼咱們就要想要怎麼去實現它。遞歸
咱們能夠觀察上圖,對於線段樹中的每個非葉子節點[a,b],它的左兒子表示的區間均爲[a,(a+b)/2],右兒子表示的區間均爲[(a+b)/2+1,b],
所以咱們利用線段樹的這一特色,能夠遞歸的將這棵線段樹構造出來,遞歸的終止條件也就是咱們構造到了葉節點,即此時線段的左右區間相等。
有了以上的思路,咱們能夠得出如下構造線段樹的代碼:
-
- Node *Build(Node *_root,int left,int right){
- _root = Init(_root,left,right);
- if(left != right){
- _root -> lchild = Build(_root -> lchild,left,(left + right) / 2);
- _root -> rchild = Build(_root -> rchild,(left + right)/2+1,right);
- }
- return _root;
- }
爲了檢驗構造狀況是否和上述圖示一致,咱們能夠利用樹的中序遍歷,查看每一個節點存儲的線段,所以咱們得出如下完整代碼:
- #include<cstdio>
- #include<cstdlib>
-
- typedef struct node Node;
-
- struct node{
- int leftvalue;
- int rightvalue;
- Node *lchild;
- Node *rchild;
- };
-
- int flag = 1;
- Node *Init(Node *_node,int lvalue,int rvalue){
- _node = (Node *)malloc(sizeof(Node));
- _node -> lchild = NULL;
- _node -> rchild = NULL;
- _node -> leftvalue = lvalue;
- _node -> rightvalue = rvalue;
- return _node;
- }
-
-
- Node *Build(Node *_root,int left,int right){
- _root = Init(_root,left,right);
- if(left != right){
- _root -> lchild = Build(_root -> lchild,left,(left + right) / 2);
- _root -> rchild = Build(_root -> rchild,(left + right)/2+1,right);
- }
- return _root;
- }
-
-
- void inorder(Node *_node){
- if(_node){
- inorder(_node -> lchild);
- printf("第%d個遍歷的節點,存儲區間爲:[%d,%d]\n",flag++,_node -> leftvalue,_node -> rightvalue);
- inorder(_node -> rchild);
- }
- }
-
- int main(){
- Node *root;
- root = Build(root,1,10);
- inorder(root);
- return 0;
- }
運行結果:

咱們發現,存儲的結果與一開始定義的徹底一致,因而咱們便成功的創建好了一棵空的線段樹。
3.線段樹的一些簡單應用:
(1).區間查詢問題:
RMQ(Range Minimum/Maximum Query),即區間最值查詢,是指這樣一個問題:對於長度爲n的數列A,回答若干詢問RMQ(A,i,j)(i,j<=n),返回數列A中下標在i,j之間的最小/大值。
咱們以RMQ爲例:即在給定區間內查詢最小值,假設咱們已經將對應區間的最小值存入了線段樹的節點中,那麼咱們利用剛剛創建好的線段樹來解決這一問題。
若是查詢的區間是[1,2],[3,3]這樣的區間,那麼咱們直接找到對應節點解決這一問題便可。可是若是查詢的區間是[1,6],[2,7]這樣的區間時,咱們能夠發如今線段樹中,沒法找到這樣的節點,
可是呢,咱們能夠找到樹中哪幾個節點可以組成咱們所要求的區間,而後再取這幾個區間內的最小值不就解決問題了嗎?
所以有了這樣的想法,咱們對於任何在合理範圍內的查詢,均可以找到若干個相連的區間,而後將這若干個區間合併,獲得待求的區間。
一般,咱們用來尋找這樣的一個區間的簡單辦法是:
function 在節點v查詢區間[l,r]
if v所表示的區間和[l,r]交集不爲空集 if v所表示的區間徹底屬於[l,r]
選取v
else
在節點v的左右兒子分別查詢區間[l,r]end if end if
end function
僞代碼出自《線段樹》講稿 楊戈
所以根據以上僞代碼咱們能夠得出如下完整代碼:
- #include<cstdio>
- #include<cstdlib>
-
- typedef struct node Node;
-
- struct node{
- int leftvalue;
- int rightvalue;
- Node *lchild;
- Node *rchild;
- };
-
- int flag = 1;
- Node *Init(Node *_node,int lvalue,int rvalue){
- _node = (Node *)malloc(sizeof(Node));
- _node -> lchild = NULL;
- _node -> rchild = NULL;
- _node -> leftvalue = lvalue;
- _node -> rightvalue = rvalue;
- return _node;
- }
-
-
- Node *Build(Node *_root,int left,int right){
- _root = Init(_root,left,right);
- if(left != right){
- _root -> lchild = Build(_root -> lchild,left,(left + right) / 2);
- _root -> rchild = Build(_root -> rchild,(left + right)/2+1,right);
- }
- return _root;
- }
-
-
- void inorder(Node *_node){
- if(_node){
- inorder(_node -> lchild);
- printf("第%d個遍歷的節點,存儲區間爲:[%d,%d]\n",flag++,_node -> leftvalue,_node -> rightvalue);
- inorder(_node -> rchild);
- }
- }
-
- void Query(Node *_node,int left,int right){
-
- if(right >= _node -> leftvalue && left <= _node -> rightvalue){
-
- if(left <= _node -> leftvalue && right >= _node -> rightvalue){
- printf("[%d,%d]\n",_node -> leftvalue,_node -> rightvalue);
- }
- else{
- Query(_node -> lchild,left,right);
- Query(_node -> rchild,left,right);
- }
- }
-
- }
-
- int main(){
- Node *root;
- root = Build(root,1,10);
- inorder(root);
- printf("區間[2,7]由線段樹中如下區間構成:\n");
- Query(root,2,7);
- return 0;
- }
咱們以區間[2,7]爲例,得出如下運行結果:

(2).區間修改操做:
在這裏咱們依然以RMQ問題爲例,假如一開始的時候,線段樹中每一個節點的權值都是1,那麼如今我要作的是,指定一個合法的區間,而後對這個區間內全部的數加上或者減去某個數,若是咱們按照區間的內的數一 一的
去遍歷並修改線段樹的節點的話,那麼改動的節點數必然遠遠超過O(logn)個,並且會存在大量的重複遍歷操做,那麼要怎麼樣才能提升程序的效率呢?
首先,咱們考慮給定的修改區間,按照前面咱們討論過的問題,咱們能夠把待操做區間變成幾個相連的子區間,那麼咱們試想,當咱們要修改一個給定區間時,咱們對其全部子區間進行修改,這樣的話不就把整個
待修改區間處理完畢了嗎?這樣的話咱們是否能夠只經過修改幾個子區間節點的值,而不考慮它們的孩子節點,就完成全部的操做了呢?
實際上,若是不考慮這些子區間的孩子節點的話,是錯誤的,由於在父親節點所帶的權值發生變化時,好比說上圖示中區間[1,2]中每一個值都加上5,那麼咱們把線段樹中表示區間[1,2]的節點修改完畢是否就能夠了呢?
答案顯然是錯誤的,由於該節點的左孩子([1,1])和右孩子節點所表示的區間([2,2])中的值也都發生了變化。
因此在這裏咱們爲了方便,咱們在節點定義中加入一個標記的量,用來存儲對節點的修改狀況。顯然,當咱們自上而下的訪問某節點時,父親節點的標記要"傳給"孩子節點,即修改大的區間,其子區間也必然被改動。
有了以上的分析,咱們能夠總結操做:
首先找到樹中哪幾個節點表示的區間,可以組成咱們待修改的區間,而後從這些節點開始向下遍歷,將以這些節點爲根節點的子樹節點權值作相應的改變。(邊查找對應子區間,邊修改權值)
完整代碼以下:
- #include<cstdio>
- #include<cstdlib>
-
- typedef struct node Node;
-
- struct node{
- int leftvalue;
- int rightvalue;
- Node *lchild;
- Node *rchild;
- int weight;
- int mark;
- };
-
- int flag = 1;
- Node *Init(Node *_node,int lvalue,int rvalue){
- _node = (Node *)malloc(sizeof(Node));
- _node -> lchild = NULL;
- _node -> rchild = NULL;
- _node -> leftvalue = lvalue;
- _node -> rightvalue = rvalue;
- _node -> weight = 1;
- _node -> mark = 0;
- return _node;
- }
-
-
- Node *Build(Node *_root,int left,int right){
- _root = Init(_root,left,right);
- if(left != right){
- _root -> lchild = Build(_root -> lchild,left,(left + right) / 2);
- _root -> rchild = Build(_root -> rchild,(left + right)/2+1,right);
- }
- return _root;
- }
-
-
- void inorder(Node *_node){
- if(_node){
- inorder(_node -> lchild);
- printf("\n第%d個遍歷的節點,存儲區間爲:[%d,%d]\n",flag,_node -> leftvalue,_node -> rightvalue);
- printf("\n第%d個遍歷的節點,權值爲%d,標記爲%d\n",flag++,_node -> weight,_node -> mark);
- inorder(_node -> rchild);
- }
- }
-
- void Query(Node *_node,int left,int right){
-
- if(right >= _node -> leftvalue && left <= _node -> rightvalue){
-
- if(left <= _node -> leftvalue && right >= _node -> rightvalue){
- printf("[%d,%d]\n",_node -> leftvalue,_node -> rightvalue);
- }
- else{
- Query(_node -> lchild,left,right);
- Query(_node -> rchild,left,right);
- }
- }
-
- }
-
-
- void change(Node *node){
- if(node){
- if(node -> lchild){
- node -> lchild -> mark += node -> mark;
- node -> lchild -> weight += node -> lchild -> mark;
- change(node -> lchild);
- }
- if(node -> rchild){
- node -> rchild -> mark += node -> mark;
- node -> rchild -> weight += node -> rchild -> mark;
- change(node -> rchild);
- }
- }
- }
-
- void update(Node *node,int left,int right,int pos){
-
-
- if(right >= node -> leftvalue && left <= node -> rightvalue){
- if(left <= node -> leftvalue && right >= node -> rightvalue){
- node -> mark = pos;
- node -> weight += node -> mark;
-
- change(node);
- }
- else{
- update(node -> lchild,left,right,pos);
- update(node -> rchild,left,right,pos);
- }
- }
- }
-
-
-
- int main(){
- Node *root;
- root = Build(root,1,4);
-
- update(root,1,3,5);
- inorder(root);
- return 0;
- }
運行結果: