线段树segment_tree go语言实现
線段樹(segment tree)
什么是線段樹
線段樹是一種二叉搜索樹,什么叫做二叉搜索樹,首先滿足二叉樹,每個結點度小于等于二,即每個結點最多有兩顆子樹,何為搜索,我們要知道,線段樹的每個結點都存儲了一個區間,也可以理解成一個線段,而搜索,就是在這些線段上進行搜索操作得到你想要的答案。
線段樹是建立在線段的基礎上,每個結點都代表了一條線段[a,b]。長度為1的線段稱為元線段。非元線段都有兩個子結點,左結點代表的線段為[a,(a + b) / 2],右結點代表的線段為[((a + b) / 2)+1,b]。
長度范圍為[1,L] 的一棵線段樹的深度為log (L) + 1。這個顯然,而且存儲一棵線段樹的空間復雜度為O(L)。
線段樹支持最基本的操作為插入和刪除一條線段。下面以插入為例,詳細敘述,刪除類似。
將一條線段[a,b] 插入到代表線段[l,r]的結點p中,如果p不是元線段,那么令mid=(l+r)/2。如果b<mid,那么將線段[a,b] 也插入到p的左兒子結點中,如果a>mid,那么將線段[a,b] 也插入到p的右兒子結點中。
插入(刪除)操作的時間復雜度為O(logn)。
線段樹能解決的問題
線段樹的適用范圍很廣,可以在線維護修改以及查詢區間上的最值,求和。更可以擴充到二維線段樹(矩陣樹)和三維線段樹(空間樹)。對于一維線段樹來說,每次更新以及查詢的時間復雜度為O(logN)。
上面說的太官方了,說淺顯一點,線段樹主要為了處理區間算法的一些問題,比如我們想對一個區間的所有數據進行求和,你可以使用for循環進行,但是每次都使用for循環這種形式進行計算的方式真的太浪費時間了,for循環的時間復雜度為O(N),我們將其替換成線段樹就可以將時間復雜度降低到O(
logn)。如果是區間求和線段樹的value就是記錄當前區間的和左+右,如果是區間求積那么value就是 左*右。一次類推,經過遞歸之后,只要每個節點都能保證自己是其左子樹和右子樹的左?右,就可以實現區間的求值,同時也將原來的復雜度O(N)降低為O(logn),這也就是為什么線段樹明明添加了更多的內存,確那么受歡迎的原因。
不要帶著線段樹只是為了解決區間問題的數據結構,事實上,是線段樹多用于解決區間問題,并不是線段樹只能解決區間問題,首先,我們得先明白幾件事情。
線段樹創建、查詢與更新
定義一個線段樹
// 以下線段樹主要目的就i事 查詢各個值得和 type mergeFunc func(i, j int) int // SegmentTree 線段樹的定義 type SegmentTree struct {data, tree, lazy []int //< data 原數據, tree 各個子節點之和left, right intmerge mergeFunc }線段樹初始化
線段樹初始化就是將一個區間從中間"均分",直到不能再分為止,偽代碼如下:
if L == R then this = L elsebuild left_child, L, (L+R)/2build left_child, (L+R)/2+1, R if value[left_child] < value[right_child]this = left_child else this = right_childgo實現代碼
// Init 線段樹 初始化 func (st *SegmentTree) Init(nums []int, op mergeFunc) {st.merge = opdata, tree, lazy := make([]int, len(nums)), make([]int, 4 * len(nums)), make([]int, 4 * len(nums))// 將線段樹中中需要存儲的數據,存儲到data中for i := 0; i < len(nums); i ++ {data[i] = nums[i]}st.data, st.tree, st.lazy = data, tree, lazyif len(nums) > 0 {// 構建線段樹st.BuildSegmentTree(0, 0 , len(nums) - 1)} }// BuildSegmentTree 構造線段樹 func (st *SegmentTree) BuildSegmentTree(treeIndex, left, right int) {// 如果left = right說明已經走到了葉節點if left == right {st.tree[treeIndex] = st.data[left]return}midTreeIndex, leftTreeIndex, rightTreeIndex := left + (right - left) >> 1, st.LeftChild(treeIndex), st.RightChild(treeIndex)st.BuildSegmentTree(leftTreeIndex, left, midTreeIndex)st.BuildSegmentTree(rightTreeIndex, midTreeIndex, right)// 當前節點的sum值,是左 + 右st.tree[treeIndex] = st.merge(st.tree[leftTreeIndex], st.tree[rightTreeIndex]) }func (st *SegmentTree) LeftChild(index int) int {return 2 * index + 1 }func (st *SegmentTree) RightChild(index int) int {return index * 2 + 2 }線段樹的范圍查詢查詢
如果需要查詢的范圍和當前節點完全匹配時,該方法返回結果,否則更深入地遍歷線段樹,找到一部分完全匹配的節點。時刻記著,當前節點的值由其左 子樹和右子樹共同決定
//Query 查詢 [left, right]區間的值 func (st *SegmentTree) Query(left, right int) int {if len(st.data) > 0 {return st.QueryInTree(0, 0, len(st.data) - 1, left, right)}return 0 }// QueryInTree 在一 treeIndex位根的線段樹中 [left .... right]的范圍內,搜索區間 [queryLeft...queryRight]的值 func (st *SegmentTree) QueryInTree(treeIndex, left, right, queryLeft, queryRight int) int {if left == queryLeft && right == queryRight {return st.tree[treeIndex]}midTreeIndex, leftTreeIndex, rightTreeIndex := left+(right-left)>>1, st.LeftChild(treeIndex), st.RightChild(treeIndex)// 說明在樹右分支if queryLeft > midTreeIndex {return st.QueryInTree(rightTreeIndex, midTreeIndex+1, right, queryLeft, queryRight)} else if queryRight <= midTreeIndex {return st.QueryInTree(leftTreeIndex, left, midTreeIndex, queryLeft, queryRight)}// 返回 左 + 右return st.merge(st.QueryInTree(leftTreeIndex, left, midTreeIndex, queryLeft, midTreeIndex),st.QueryInTree(rightTreeIndex, midTreeIndex+1, right, midTreeIndex+1, queryRight)) }懶查詢
與懶查詢對應的是懶更新,或者說兩者是配套使用的。當區間更新時,并不是使用遞歸對區間所有的值進行更新,而是把區間內節點增減的變化的值存儲到lazy數組中去,等下次查詢時再把增減少應用到具體的節點上。這樣做是為了分攤時間復雜度,保證查詢和更新的時間復雜度在O(logn)級別,不會退化到O(n)級別
懶查詢步驟:
單點更新
單點更新是直接對樹的葉子節點進行更新,并通過由葉->根的順序將影響逐步傳遞到根節點
區間更新
更新[updateLeft, updateRight]區間的值,這里的更新在區間值原有的基礎上增加或者減少值,而不是把區間的值都賦值為x,區間更新關注的是變化,單點更新關注的是定值。當然區間跟新也可以是定值,這里暫且不討論這種情況。
線段樹對單個節點的更新非常有效,時間復雜度為O(logn),要是更新一系列的元素就需要每個元素都單獨更新下。這就造成根節點被多次更新就行了重復計算。在圖中根節點被更新了3次,82節點被更新了兩次,最差的情況是查詢到節點不需要更新,但同樣需要更新節點的值。添加lazy節點就能避免這種不必要的更新。
使用一個數組lazy,代表惰性節點,當訪問或者查詢該節點時,該節點就存儲對應tree[i]節點增加或者減少的數量,當lazy[i]為0時代表對應節點為非惰性節點,并不需要更新,只有惰性節點才需要更新tree[i]的值。
更新區間節點的步驟:
關注微信公眾號獲取更多信息
總結
以上是生活随笔為你收集整理的线段树segment_tree go语言实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 许海燕(1987-),女,宁波市智慧城市
- 下一篇: LFUCache