線段樹基礎

2021-07-04 00:04:37 字數 2731 閱讀 5922

寫得不錯啊。。。

前段時間寫了篇「搞懂樹狀陣列」,如果說樹狀陣列是優雅的,那麼線段樹就是萬能的。有句話就叫:樹狀陣列能做的線段樹都能做,但是樹狀陣列能做的堅決用樹狀陣列!因為線段樹本來的內容狠豐富的,主要有單點跟新、區間跟新,最值詢問、區間詢問…………反正就是對於區間進行的動態跟新詢問都能較高效的完成。

對於初學者,一定要明白一點:線段樹就是一種可以較高效的動態的對區間進行跟新查詢!至於它具體能幹哪些事取決於你樹里所儲存的資訊量!不要一開始就想要什麼通用的模板,模板可能有,但是如果你不理解它,恐怕也用的不順心。其實,線段樹的實現本省**量不是很大,複雜性也不是很高,完全可以每次根據題目具體的需求寫出來。當然,前提是自己理解了。先來看一幅圖:

它就是一顆完全二叉樹,根節點是乙個大區間,兩個兒子節點是根節點區間一分為二。當然,最重要的東西圖中並沒有體現出來,它只是描述了節點之間的邏輯關係,而我沒實現它的時候一般都要為節點(即所表示的區間)附加點屬性:最大值、最小值、和。。。。

現在我們就要了解如何實現它了,如果每個節點只有乙個簡單的附加屬性,完全可以用陣列實現。我們對上面的節點從上到下、從左到右一次標號1、2、……、m,可以看出節點1的兩個兒子分別是2和3,以此類推節點i的左右兒子分別是2*i和2*i+1,用位運算可以表示為i<<1和i<<1|1,所以我們可以用乙個陣列tree來表示這個附加屬性,而下表則是節點的標號。那麼,這個陣列申請多大合適呢?根據樹的理論,如果區間範圍是[0,n-1],則m=2*n+1,這個很容易驗證,自己可以試試。接下來就要考慮有哪些操作了,一般來說有三個操作:建樹、更新、查詢。

比如對於單點更新,查詢某個區間的最大值,可以用下面的**簡單實現

[cpp]view plain

copy

print?

inttree[2*max_n+1];  

/*建立以k為根節點[l,h]為操作區間的線段樹*/

void

built_tree(

intk, 

intl, 

inth)  

built_tree(k <

built_tree(k <

}  /*在根節點為k,[l,h]為操作區間的線段樹里對id處的值更新為key*/

intupdate_tree(

intk, 

intl, 

inth, 

intid, 

intkey)  

if(id 

update(k*2, l, (l + h)/2, id, key) ;  

else

update(k*2 + 1, (l + h)/2 + 1, h, id, key);  

tree[k] = max(tree[k*2], tree[2*k + 1]);  

}  /*在根節點為k,[l,h]為操作區間的線段樹里查詢區間[beg,end]的最大值*/

intread_tree(

intk, 

intl, 

inth, 

intbeg, 

intend)    

用的是遞迴實現的,複雜度都是o(lgn),可能有人就疑問了,為什麼用線段樹就能節省時間呢?其實也不能算節省,它把時間平均了,避免了「尖端分子」影響整個效能!比如,如果我們不用線段樹,用一段方法實現的話,那麼更新操作其實是o(1)的複雜度,而查詢和建樹就變成了o(n)了。還有,線段樹也多犧牲了點記憶體,這就是有得必有失嘛~

上面的例子是每個節點只有乙個屬性的情況,加入現在有多個屬性呢?那麼tree的值表示什麼呢?除非你再定義乙個tree_other。其實更好的寫法是用鍊錶實現,這樣每個煉表裡就可以包含多個屬性了,我們把上面的例子用鍊錶再寫一下,目的是為了搞明白,而不是給大家提供模板!因為給的**我也是現場寫的。

[cpp]view plain

copy

print?

typedef

struct

_treetree;  

tree *init_tree(int

l, int

h)  

else

return

treep;  

}  void

update(tree *root, 

intid, 

intkey)  

if(id h + root->l)/2) else

root->mmax = max(root->left->mmax, root->right->mmax);  

}  int

read(tree *root, 

intbeg, 

intend)    

其實,寫法都差不多,記住:不是為了給模板,只是為了幫助理解,因為寫的確實不是很養眼。。。

其實,如果把上面的都理解的差不多的話,線段樹算基本掌握了吧。但是,還有個東西,有必要提一下:在進行區間更新的時候,有個懶操作的技巧。解釋一下:就是每個節點增加了乙個屬性,用來記錄這個對應的區間是否被更新過(或更新過多少)。這樣,在每次更新操作的時候不用馬上一直遞迴更新下去,只有等到下次更新或查詢的時候才順帶的更新到下一級。這個實現的話,就是在煉表裡增加乙個屬性,每次更新或查詢的時候都要判斷是否被標記(即之前這個區間是否被更新過),如果標記有效,則把它的兩個兒子節點對應的區間也標記了。這其實也是一種平均的作用,避免「極端分子」。

差不多了吧,就寫到這吧,有點感冒不舒服,以後如果想到什麼再新增吧~

線段樹 01 線段樹基礎

物理上 public class segmenttree public int getsize public e get int index 返回完全二叉樹的陣列表示中,乙個索引所表示的元素的左孩子節點的索引 private int leftchild int index 返回完全二叉樹的陣列表示中...

線段樹 基礎

d 基礎 time limit 1000msmemory limit 32768kb64bit io format i64d i64u submit status description c國的死對頭a國這段時間正在進行軍事演習,所以c國間諜頭子derek和他手下tidy又開始忙乎了。a國在海岸線沿...

線段樹基礎

平衡二叉樹是完全二叉樹,演算法複雜度為o logn 級別,建樹用二分法找到左右兩個子節點,直到不能繼續劃分節點,線段樹主要用於處理一段連續區間的插入 查詢 統計 查詢等操作,線段樹的執行時間主要是 1 對於任意兩個節點的區間,要麼完全包含,要麼互不相交 線段樹模板 void build int no...