樹結構的基本思想是分割,普通二叉搜索樹是按照數據來劃分(想了解二叉搜索樹的請移步:Here),線段樹處理的對象是線段(區間也可以看成線段,L==R時爲一個點),它把線段組織成有利於檢索和統計的形式,它的本質是線段的二叉搜索樹。但是它的線段可以分解和合並,線段樹又有一些一般二叉檢索樹沒有的特殊操作。另外線段樹操作的是整個區間,它的時間複雜度不依賴於數據對象。它將一個區間劃分成一些單元區間,每個單元區間對應線段樹中的一個葉結點。使用線段樹可以快速的查找某一個節點在若干條線段中出現的次數,時間複雜度爲O(logN)。而未優化的空間複雜度爲2N,因此有時需要離散化讓空間壓縮。
線段樹的定義:
線段樹是一棵二叉檢索樹,最終將一個區間 [1,n] 劃分爲一些 [ i,i+1 ]的但願區間,每個單元區間對應一個葉節點。葉節點區間只有一個數(L == R)。對於每個線段樹的非葉子節點 [ a, b ],其左兒子 爲 [a,(a+b)/2],右兒子爲 [ (a+b)/2 + 1,b]。所以線段樹是一顆平衡二叉樹。最後節點數目爲 N 即整段區間的長度,其節點數爲 n + n/2 + n/4 + … = 2n。所以其空間複雜度爲 O(2n)。
時間複雜度爲 log n(插入或者讀取均爲log n)。其結構如圖
線段樹的處理對象:
線段樹的處理對象是一段區間,區間上的格點對應有限個區域的變量,處理問題的時候,抽出區間上的格點,也是明確每個格點對應變量的含義。
線段樹的基本結構:
一棵線段樹,應該具有 插入 , 修改 , 查詢 三個基本功能。
【代碼實現】:
我們以區間最值問題(RMQ)爲例:
我們先聲明線段樹所需的結構體
[cpp] view plain copy
#include <stdio.h>
#define MAXN 1<<19
typedef struct
{
int value; //區間最值
int left,right; //區間範圍
}Tree;
Tree node[2*MAXN];
int father[MAXN]; //記錄葉子對應結構體的 下標</span>
如圖所示:
如果 二叉樹 的 父節點 爲 k,那麼其 左兒子爲 2k,右兒子 爲 2k+1。
現在我們可以建立線段樹裏。
[cpp] view plain copy
//線段樹的建立
void build(int i, int left, int right){ //i爲結構體數組的下標
node[i].left = left; //爲節點成員初始化
node[i].right = right;
node[i].value = 0;
if(left == right){ //當線段樹的節點爲葉子時,結束遞歸
father[left] = i;//將葉子在結構體數組的下標記錄,以便更新是可以自下而上
return ;
}
//現在分別建立該節點的左右孩子
build(i<<1,left,(left+right)/2);
build( (i<<1)+1,1+(left+right)/2,right);
return ;
}
因爲我們現在是以最值得問題作爲樣例,所以我們更新數據的話就是單點操作,這裏最值我們以最大值爲例
[cpp] view plain copy
//自上往下的更新,n_i 如上圖所意
void Updata(int n_i){
if(n_i == 1) return ; //找到了根節點,結束遞歸
int fa = n_i/2; //找到了父節點
int a = node[2*fa].value; //該父節點的左兒子的值
int b = node[2*fa + 1].value;//該父節點的右兒子的值
node[fa].value = a>b?a:b; //更新節點數據
Updata(fa); //遞歸更新
return ;
}
現在我們剩下的就差查詢操作了
[cpp] view plain copy
int Max = -MAXN;
//k爲結構體下標,通常我都從根節點開始查詢,所以,通常我們初始化時爲1
//查詢區間爲 [ left, right ]
void Query(int k,int left,int right){
//當查詢區間完全重合時
if(node[k].left == left && node[k].right == right){
Max = Max > node[k].value ? Max : node[k].value;
return ;
}
//對左子樹進行操作
if(left <= node[2*k].right){ //如果與左區間有交集
if(right <= node[2*k].right) //如果完全包含於左區間,則查詢範圍不變
Query(2*k,left,right);
else//否則這將區間查分開,先查詢左邊的
Query(2*k,left,node[2*k].right);
}
//對右子樹進行操作
if(right >= node[2*k+1].left){ //如果與右區間有交集
if(left >= node[2*k+1].left) //如果完全包含於右區間,則查詢範圍不變
Query(2*k+1,left,right);
else//否則這將區間查分開,先查詢右邊的
Query(2*k+1,node[2*k+1].left,right);
}
return ;
}