線段樹是一種二叉查詢樹,它將乙個區間劃分為1個個單元,樹的每個節點都是1個單元。如下圖的樹就是一顆區間樹。
性質
對於線段樹中的每乙個非葉子節點[a, b],它的左節點為[a, (a+b)/2],右節點為[(a+b)/2+1, b]。
線段數是平衡二叉樹,子節點的個數等於整個區間的長度。
建樹
在這裡,我們使用陣列來實現簡單的線段樹,樹的結構如下。
public
class
segmenttree
}
public
inte***ce
merger
如下圖所示。merge介面可以求和、求最大/最小值等。
在上述的**中,tree的陣列容量我們取節點個數的4倍。為什麼這樣做呢?假如區間有n個元素,用陣列表示需要多少個節點?為方便說明,我們取節點個數為2^3,即n=8。我們看下構成的線段樹。
在這種情況下構成的滿二叉樹中,最後一層的節點個數等於n。最後一層的節點個數等於其餘幾層的節點總數+1。也就是說大約需要2 * 8個陣列容量才能儲存整棵樹。但是在最壞的情況下,節點總數n等於2的m次冪加1。以n=9為例,這棵樹的層數還要加1。所以我們一般直接取4n作為線段樹的陣列容量。如果還不懂的話,建議自己畫顆線段樹。
/**
* 構成treeidx處,表示區間[left,right]的線段樹
** @param treeidx 陣列下標
* @param left 區間左邊界
* @param right 區間右邊界
*/private
void
buildsegment
(int treeidx,
int left,
int right)
int leftchild =
getleftchild
(treeidx)
;int rightchild =
getrightchild
(treeidx)
;int mid = leftchild +
(right - left)/2
;buildsegment
(leftchild, left, mid)
;buildsegment
(rightchild, mid +
1, right)
; tree[treeidx]
= merger.
merge
(tree[leftchild]
, tree[rightchild]);
}/**
* 獲取左子孩子的陣列下標
** @param parent 父節點下標
* @return 左子節點下標
*/private
intgetleftchild
(int parent)
/** * 獲取右子孩子的陣列下標
** @param parent 父節點下標
* @return 右子節點下標
*/private
intgetrightchild
(int parent)
在這裡我們使用遞迴來構造線段樹,**很簡單。唯一要注意的是獲取左/右孩子節點的陣列下標。還是以剛才[1,8]的線段樹為例。我們看下tree這個陣列是怎麼儲存整個樹的。如下所示,父節點的小標為p,那麼左子節點的下標為2 * p + 1,右子節點的下標為 2 * p + 2。
區間查詢
區間查詢分為3種情況,如查詢區間[left,right]:
right <= mid:資料分布在左右子樹,在[left, mid]查詢[left,right]。
left > mid:資料分布在右子樹,[mid + 1, right]中查詢[left, right]。
left <= mid < right:資料分布在左右子樹,在[leftbound, mid]中查詢[left, mid],在[mid+1, rightbound]查詢[mid+1, right],再進行合併操作。leftbound和rightbound為整個區間的邊界值。
如在[1,8]的線段樹中,查詢[2,5]這個區間。我們來看下查詢的過程,黃色部分為區間查詢所走的路徑。紅色部分為查詢到的結果。
* 查詢區間[left, right]
** @param left 區間左邊界
* @param right 區間右邊界
* @return
*/public e query
(int left,
int right)
//從跟節點開始搜尋
return
query(0
,0, data.length -
1, left, right);}
/** * 在以treeidx為根節點的線段樹[leftbound, rightbound]中,搜尋[left, right]的值
** @param treeidx 根節點下標
* @param leftbound 線段樹的左邊界
* @param rightbound 線段樹的右邊界
* @param left 查詢的左邊界
* @param right 查詢的右邊界
* @return 搜尋出的值
*/private e query
(int treeidx,
int leftbound,
int rightbound,
int left,
int right)
int mid = leftbound +
(rightbound - leftbound)/2
;int leftchild =
getleftchild
(treeidx)
;int rightchild =
getrightchild
(treeidx);if
(right <= mid)
else
if(left > mid)
else
}單點更新更新與查詢類似,單點更新只需要更新某個葉子節點的值,但是更新葉子節點會對其父節點的值產生影響,因此更新子節點後,也同樣要更新父節點的值。
/**
* 更新
* @param elementidx 元素下標
* @param element 元素
* @return
*/public e set
(int elementidx, e element)
/** * 更新以treeidx為跟節點的線段樹的位置在elementidx的值
** @param treeidx 根節點下標
* @param leftbound 左區間
* @param rightbound 右區間
* @param elementidx 更新的元素的小標
* @param element 更新的元素
*/private
void
set(
int treeidx,
int leftbound,
int rightbound,
int elementidx, e element)
int mid = leftbound +
(rightbound - leftbound)/2
;int leftchild =
getleftchild
(treeidx)
;int rightchild =
getrightchild
(treeidx);if
(elementidx <= mid)
else
}
資料結構之線段樹
線段樹也叫區間樹,顧名思義,線段樹是一種基於區間的樹,每個節點表示乙個 線段 或 區間 樹的根節點表示是 整體 的區間,左右子樹分別表示這個區間的左半邊和右半邊。function 以節點v為根建樹 v對應區間為 l,r 線段樹的關鍵在於如何定義樹節點,以及如果構建 插入 樹節點。1.樹節點的定義 p...
資料結構之線段樹
一 引例 有m個數排成一列,做n次操作,每次操作包括 1 詢問指定區間的最大值 最小值 2 將指定區間的每個數加上乙個值 如果按照最樸素的做法,乙個個的遍歷,時間複雜度 o mn 那麼如何解決乙個區間求和 最大值,最小值 的問題呢?那麼就要用到線段樹啦。二 定義 線段樹是一種二叉搜尋樹,與區間樹相似...
關於資料結構之線段樹
這幾天都一直在看關於線段樹的題目還有題解,還有做題!以前也知道有線段樹這個東西,但是那時沒有好好的看,就看了個簡單的皮毛!所以現在又拿出來好好看看!一開始看,一直看題解,還有就是模仿,首先看一遍,初步了解一下,然後就是按著別人的題解再抄著寫一遍直到通過測試,當然不是直接對著乙個乙個的copy上,然後...