搞懂線段樹

2021-08-05 20:24:59 字數 3509 閱讀 7811

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

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

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

現在我們就要了解如何實現它了,如果每個節點只有乙個簡單的附加屬性,完全可以用陣列實現。我們對上面的節點從上到下、從左到右一次標號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?

int tree[2*max_n+1];  

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

void built_tree(int k, int l, int h)  

built_tree(k <

built_tree(k <

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

int update_tree(int k, int l, int h, int id, int key)  

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]的最大值*/

int read_tree(int k, int l, int h, int beg, int end)    

int tree[2*max_n+1];

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

void built_tree(int k, int l, int h)

built_tree(k << 1, l, (l + h) << 1);

built_tree(k << 1 | 1, (l + h) << 1 | 1, h);

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

int update_tree(int k, int l, int h, int id, int key)

if(id < (l + h)/2)

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]的最大值*/

int read_tree(int k, int l, int h, int beg, int end)

用的是遞迴實現的,複雜度都是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, int id, int key)  

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

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

}  int read(tree *root, int beg, int end)    

typedef struct _treetree;

tree *init_tree(int l, int h)

else

return treep;

}void update(tree *root, int id, int key)

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

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

}int read(tree *root, int beg, int end)

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

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

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

線段樹 02 構建線段樹

public inte ce merger 不能再縮小的基本問題是 對treeindex指向的節點的情況進行討論 public class segmenttree 在treeindex的位置建立表示區間 l.r 的線段樹 private void buildsegmenttree int treei...

線段樹 01 線段樹基礎

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

線段樹和zkw線段樹

好啦,我們就開始說說線段樹吧 線段樹是個支援區間操作和查詢的東東,平時的話還是蠻實用的 下面以最基本的區間加以及查詢區間和為例 線段樹顧名思義就是棵樹嘛,葉子節點是每個基本點,它們所對應的父親就是它們的和,具體如下圖 但是對於這樣的線段樹來說,操作所需的時間是遠達不到我們的要求的 會被t 因為我們會...