【學習筆記】高級數據結構--線狀樹 poj 3264

這幾天學習ACM訓練,第一次接觸了線段樹(Interval Tree)。

現在給大家簡單說說我的一點點理解,以及poj上一道典型題目的源碼和超詳細的註釋。

線段樹:實際上稱爲區間樹更好理解一些。線段:樹上的每個節點對應於一個線段(區間)。

特點:(1)同一層的節點所代表的區間相互不會重疊。

            (2)同一層節點所代表的區間,加起來是個連續的區間。

             (3)葉子節點的區間長度是單位長度,不能再分。

線段樹是一顆二叉樹,樹中每個結點表示了一個區間[a,b]。a,b通常是整數。對於每一個非葉節點所表示的節點[a,b],其左兒子表示區間爲[a,(a+b)/2],右兒子表示區間爲[(a+b)/2+1,b]。(除法去尾取整)。

圖爲區間【1,9】的線段樹。



線段樹的深度不超過log2(n)+1。

線段樹上更新葉子節點和進行區間分解時間複雜度都是O(log(n))的。這些結論爲線段樹能在O(log(n))的時間內完成插入數據,更新數據、查找、統計等工作,提供了理論依據。


線段樹的基本用途:線段樹適用於和區間統計有關的問題。比如某些數據可以按區間進行劃分,按區間動態進行修改,而且還需要按區間多次進行查詢,那麼使用線段樹可以達到較快查詢速度。

線段樹的應用舉例

給你一個數的序列A1A2……An。並且可能多次進行下列兩個操作:
1、對序列裏面的某個數進行加減
2、詢問這個序列裏面任意一個連續的子序列AiAi+1……Aj的和是多少。
希望第2個操作每次能在log(n)時間內完成

分析:

顯然,[1,n]就是根節點對應的區間
可以在每個節點記錄該節點對應的區間裏面的數的和Sum。
對於操作1:因爲序列裏面Ai最多隻會被線段樹的log(n)個節點覆蓋。只要求對線段樹覆蓋Ai的節點的Sum進行加操作,因此複雜度是log(n)
對於操作2:同樣只需要找到區間所覆蓋的“終止”節點,然後把所找“終止”節點的Sum累加起來。因爲這些節點的數量是O(log(n))的,所以這一步的複雜度也是log(n)

如果走到節點[L,R]時,如果要查詢的區間就是[L,R]
(求AL到AR的和)那麼直接返回該節點的Sum,並累加到總的和上;
如果不是,則:
對於區間[L,R],取mid=(L+R)/2;
然後看要查詢的區間與[L,mid]或[mid+1,R]哪個有交集,就進入哪個區間進行進一步查詢。
因爲這個線段樹的深度最深的LogN,所以每次遍歷操作都在LogN的內完成。但是常數可能很大。

如果是對區間所對應的一些數據進行修改,過程和查詢類似。

總結:

用線段樹解題,關鍵是要想清楚每個節點要存哪些信息(當然區間起終點,以及左右子節點指針是必須的),以及這些信息如何高效更新,維護,查詢。不要一更新就更新到葉子節點,那樣更新效率最壞就可能變成O(n)的了。

方法:先建樹,然後插入數據,然後更新,查詢。

---------------------------------------------------------------------------------------------------------------------------------------------------------

POJ 3264 Balanced Lineup

給定Q(1 ≤ Q≤ 200,000)個數A1,A2… AQ,,多次求任一區間Ai–Aj中最大數和最小數的差。

Sample Input
6 3    //6個數,3次個查詢
1
7
3
4
2
5
1 5
4 6
2 2
Sample Output

6

3

0

源代碼:

#include<cstdio>
#include<iostream> 
#include<algorithm>
using namespace std;
const int INF=0xffffff0;
int minV= INF;
int maxV= -INF;
struct Node{
	//定義了樹中每個節點的信息 
	int L,R; //樹節點的左孩子L,右孩子R 
	int minV,maxV;  //在樹節點中記錄在其範圍內的max和min 
	int Mid(){   //尋找L和R的中間值返回 
		return (L+R)/2;
	}
};
Node tree[400010];   

void BuildTree(int root,int L,int R){
	//相當於初始化整棵樹,初始化每個樹節點,傳進來這個節點所包含的數值範圍L到R 
	tree[root].L=L;   
	tree[root].R=R;
	tree[root].minV= INF;
	tree[root].maxV= -INF;
	if(L!=R){  //遞歸調用該方法,初始化整棵樹 
		BuildTree(2*root+1,L,(L+R)/2);
		BuildTree(2*root+2,(L+R)/2+1,R);
	}
}

void Insert(int root,int i,int v){
	//將第i個數,其值爲v,插入線段樹 
	//實際上,Insert是構建線段樹的過程 
	if(tree[root].L==tree[root].R){ 
	//如果當前節點的L和R相等,說明到了葉子節點
	//對葉子節點的操作是:將葉子本身的值v定義爲該節點的max和min 
		tree[root].minV=tree[root].maxV=v;
		return ;
	}
	//如果不是葉子節點
	//則插入了新的節點此時需要更新包含這個節點在內的所有節點的max和min
	// 首先對本節點進行操作:
	//   如果新插入的v比當前節點的minV還小,就將其更新爲當前節點的minV
	//   如果新插入的v比當前節點的maxV還大,就將其更新爲當前節點的maxV
	// 其次對其後影響了的節點進行更新判斷,需要進行遞歸調用該方法。 
	tree[root].minV=min(tree[root].minV,v);
	tree[root].maxV=max(tree[root].maxV,v);
	if(i<=tree[root].Mid()){
		//如果i小於等於mid說明可能影響到了左樹的max或min
		//調用左樹進行判斷更新; 
		Insert(2*root+1,i,v);
	}else{
		//如果i大於mid說明可能影響到了右樹的max或min
		//調用右樹進行判斷更新; 
		Insert(2*root+2,i,v);
	}
	//需要說明:此時不會有第三種情況。
	//因爲這個值只可能插入這個節點的左樹和右樹這兩種情況 
}


void Query(int root,int s,int e){
	//查詢區間[s,e]中的最大值和最小值,如果更優就記在全局變量裏 
	if(tree[root].minV >= minV && tree[root].maxV <=maxV )
		return ;
	if(tree[root].L==s && tree[root].R ==e){
		//如果查詢的區間s、e就是當前節點所包含的區間
		//將所包含的最大值和最小值依次和 當前max和min比較 
		minV= min(minV,tree[root].minV);
		maxV= max(maxV,tree[root].maxV);
		return ;
	}
	//如果區間右邊界小於mid,遞歸調用查詢左節點,區間s,e ,此時root已經變成左節點 
	if(e<=tree[root].Mid())
		Query(2*root+1,s,e);
	//如果區間左邊界大於mid,遞歸查詢有節點區間s,e,注意此時root已經變成右節點 
	else if(s>tree[root].Mid())
		Query(2*root+2,s,e);
	else{
	//說明所查詢區間s,e橫跨了該節點所對應的左右節點
	//則依次調用左節點和右節點 
		Query(2*root+1,s,tree[root].Mid());
		Query(2*root+2,tree[root].Mid()+1,e);
	}
}
int main(){
	int n,q,h;
	int i,j,k;
	scanf("%d%d",&n,&q);
	//n代表一共有n個數需要建立這個線狀樹 
	//q代表一共有幾次查詢 
	BuildTree(0,1,n);
	//初始化樹節點:
	//以0號爲root,1和n爲該節點表示區間範圍[L,R] 
	for(i=1;i<=n;i++){
		scanf("%d",&h);
		Insert(0,i,h);
		//從0爲root開始,依次將第i個數,其值爲h,插入初始化的線狀樹中 
	}
	//這時候創建這棵線狀樹完畢 
	for(i=0;i<q;i++){
		//依次進行每次查詢 
		int s,e;
		scanf("%d%d",&s,&e);
		//輸入要查詢的區間[s,e] 
		minV=INF;
		maxV=-INF;
		Query(0,s,e);
		//從0爲root開始(樹根)查詢s、e區間 
		//將查詢區間的max和min存放在全局變量的maxV和minV中
		//然後輸出二者的差即爲所求 
		printf("%d\n",maxV-minV);
	}
	return 0;
}




發佈了30 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章