資料結構之線段樹

2021-09-03 03:12:04 字數 4316 閱讀 6648

線段樹是一種二叉查詢樹,它將乙個區間劃分為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上,然後...