線段樹是學習資料結構必須學習的一種資料結構,在acm,藍橋等比賽中是經常出現的。利用線段樹解題,會使得題目簡單易理解。而且線段樹是資料結構中比較基礎而且用的很多的一種。
線段樹定義
線段樹是一種二叉搜尋樹,與區間樹相似,它將乙個區間劃分成一些單元區間,每個單元區間對應線段樹中的乙個葉結點。
對於線段樹中的每乙個非葉子節點[a,b],它的左兒子表示的區間為[a,(a+b)/2],右兒子表示的區間為[(a+b)/2+1,b]。因此線段樹是平衡二叉樹,最後的子節點數目為n,即整個線段區間的長度。
使用線段樹可以快速的查詢某乙個節點在若干條線段**現的次數,時間複雜度為o(logn)。而未優化的空間複雜度為2n,因此有時需要離散化讓空間壓縮。
線段樹圖示
線段樹解決什麼問題
線段樹解決的是區間的問題,顧名思義,線段,就代表著一段區間上的問題。而線段樹則是通過樹這種資料結構來解決區間的問題。但是區間問題除了用線段樹,也可以用別的方式來解決。那麼線段樹有什麼特別之處呢?線段樹可以解決帶有更改的區間問題。最基礎的是兩種,區間求最值以及區間求和。
線段樹的基本內容
線段樹絕對不只是為了解決區間問題的資料結構,事實上,是線段樹多用於解決區間問題,並不是線段樹只能解決區間問題,首先,我們得先明白幾件事情。
每個結點存什麼,結點下標是什麼,如何建樹。
我們以區間求最值來闡述這個問題。
a陣列為[1,8,6,4,3,5].在求最大值的線段樹上建樹後的分布如上圖所示。
可以發現,每個葉子結點的值就是陣列的值,每個非葉子結點的度都為二,且左右兩個孩子分別儲存父親一半的區間。每個父親的儲存的值也就是兩個孩子儲存的值的最大值。
我們現在想的是每個節點如何儲存資料以及我們怎麼能夠快速的找到某個節點的孩子節點以及父親節點呢?這也就是線段樹的重點和難以理解的地方了。
對於乙個區間[l,r]來說,最重要的資料當然就是區間的左右端點l和r,但是大部分的情況我們並不會去儲存這兩個數值,而是通過遞迴的傳參方式進行傳遞。這種方式用指標好實現,定義兩個左右子樹遞迴即可,但是指標表示過於繁瑣,而且不方便各種操作,大部分的線段樹都是使用陣列進行表示,那這裡怎麼快速使用下標找到左右子樹呢。我們對上圖每乙個儲存結構編號。如下圖所示:
值得一問的是,為什麼最下一排的下標直接從9跳到了12,道理也很簡單,中間其實是有兩個空間的呀!!雖然沒有使用,但是他已經開了兩個空間,這也是為什麼無優化的線段樹建樹需要4倍的儲存空間,一般會開到4 * n的空間防止re。
從上圖我們可以看到,假設節點為cur,那麼它的左孩子節點為(2 * cur),右孩子節點為(2 * cur+1)。因為左子樹都是偶數,所以我們常用位運算來尋找左右子樹。
k<<1(結點k的左子樹下標)
k<<1|1(結點k的右子樹下標)
明白了一些基本的常識,那就要學著建樹了。
const
int maxx=
1e6+1;
int ary[maxx]
;struct node
a[maxx<<2]
;//開設四倍空間
inline
void
pushup
(int cur)
//pushup函式的意思是:我們更新了兩個孩子節點的最值了,那麼我們就可以通過兩個孩子節點的值來更新父親節點的值了。
void
build
(int l,
int r,
int cur)
build
(l,m,cur<<1)
;//遞迴去建樹,這裡運用了二分的思想
build
(m+1
,r,cur<<1|
1);pushup
(cur)
;//建完樹之後就往上更新。
}
線段樹的基本操作
inline
void
update
(int l,
int r,
int v,
int cur,
int pos)
int mid=l+r>>1;
//找到這個節點的中點
if(pos<=mid)
update
(l,mid,v,cur<<
1,pos)
;//左區間
else
update
(mid+
1,r,v,cur<<1|
1,pos)
;//右區間
pushup
(cur)
;//往上更新
}
②區間查詢
說完了單點更新肯定就要來說區間查詢了,我們知道線段樹的每個結點儲存的都是一段區間的資訊 ,如果我們剛好要查詢這個區間,那麼則直接返回這個結點的資訊即可,比如對於上面線段樹,如果我直接查詢[1,6]這個區間的最值,那麼直接返回根節點資訊返回13即可,但是一般我們不會湊巧剛好查詢那些區間,比如現在我要查詢[2,5]區間的最值,這時候該怎麼辦呢,我們來看看哪些區間是[2,5]的真子集。如下圖所示
畫黃顏色的就是[2,5]的真子集,但是我們可以看到[4,4]和[5,5]是被[4,5]包括的,而且[4,5]的最值我們也是知道的,這樣我們就只查尋三個區間就可以了。我們還是從根節點開始往下遞迴,如果當前結點是要查詢的區間的真子集,則返回這個結點的資訊且不需要再往下遞迴了,這樣從根節點往下遞迴,時間複雜度也是o(logn)。
**如下:
inline
intquery
(int l,
int r,
int cur)
線段樹的基本操作就是這些了。自己動手模擬一下然後多做題就能掌握了。
參考部落格:
努力加油a啊,(o)/~
資料結構 線段樹(單點更新 )
如果有錯誤,歡迎大神指出!線段樹 模板 線段樹是一種二叉搜尋樹,與區間樹相似,它將乙個區間劃分成一些單元區間,每個單元區間對應線段樹中的乙個葉結點。對於線段樹中的每乙個非葉子節點 a,b 它的左兒子表示的區間為 a,a b 2 右兒子表示的區間為 a b 2 1,b 因此線段樹是平衡二叉樹,最後的子...
單點更新,區間查詢線段樹
線段樹的空間複雜度是4n include include include using namespace std const int maxn 100005 const int inf 0x3f3f3f3f int n,a maxn struct node 結點 tree maxn 4 樹結點儲存陣...
線段樹 學習 模板 單點更新 區間更新
線段樹是幹什麼的?有一列數,每次可以進行以下三種操作中的一種 1 給指定區間中的每個數都加上某個值 2 將指定區間內的所有數置成某乙個統一的值 3 詢問乙個區間上的最小值 最大值 所有數的和。樸素做法怎麼做?用線性表儲存,每種操作,對待處理或待詢問區間中的每個元素都逐一進行處理。複雜度多少?假設這個...