LeetCode 307 Medium 區間和查詢 分段樹 Python
阿新 • • 發佈:2018-12-31
演算法:分段樹 SegmentTree
思路:
將nums每次對半分段,構建分段樹,分段樹節點儲存nums[left,right]的區間和
分段樹:
線段樹是一種平衡二叉搜尋樹(完全二叉樹),它將一個線段區間劃分成一些單元區 間。對於線段樹中的每一個非葉子節
點[a,b],它的左兒子表示的區間為[a,(a+b)/2],右 兒子表示的區間為[(a+b)/2+1,b],最後的葉子節點數目為N,與
陣列下標對應。線段樹 的一般包括建立、查詢、插入、更新等操作,建立規模為N的時間複雜度是 O(NlogN),其他操作時
間複雜度為O(logN)。
分段樹是完全二叉樹,可以用一維陣列儲存
class NumArray: def __init__(self, nums): """ 建立分段樹: 由於分段樹最頂層的根節點儲存的是[0,len(nums)]的區間和左子樹儲存的是[0,len(nums)/2]的區間和, 右子樹儲存[len(nums)/2+1,len(nums)]區間和,以此類推,所以可以用遞迴的方式建立 [0,len(nums)-1] / \ [0,len(nums)/2] [len(nums)/2+1,len(nums)] / \ .... .... 如果用TreeNode的結構,TreeNode應該是如下形式: class SegmentTreeNode: self.segment_sum = X self.left_segment = i self.right_segment = j self.left_child = None self.right_child = None 由於這裡用一維陣列來實現,而上述SegmentTreeNode的內容又都必須包括,就需要在遞迴的過程中,計算並傳遞left,right 而segemnt_sum則可以用values一維陣列來儲存,其中valuse的下標pos來表徵當前節點與左孩子和右孩子的關係, 當前節點的下標為pos,則: left_child at --> pos*2+1 right_child at --> pos*2+2 所以在建立過程中,如果left == right,則說明抵達了葉子節點,values[pos] = nums[left] 否則的話,找到[left,right]的中間位置mid進行左右子樹分割,左子樹的區間段是[left,mid],右子樹的區間段是[mid+1,right] 計算出左子樹和右子樹的segment_sum後,當前pos位置的segment_sum值= values[left_pos]+values[right_pos] """ if nums == []: return self.nums_end = len(nums) - 1 self.values = [0] * 4 * len(nums) #小象學院PDF說一般線段樹陣列長度是原len(nums)的4倍 def build_segment_tree(pos, left, right): if left == right: self.values[pos] = nums[left] return mid = (left + right) // 2 build_segment_tree(pos * 2 + 1, left, mid) # left child at pos*2+1 build_segment_tree(pos * 2 + 2, mid + 1, right) # right child at pos*2+2 self.values[pos] = self.values[pos * 2 + 1] + self.values[pos * 2 + 2] build_segment_tree(0, 0, self.nums_end) def update(self, i, val): """ 更新分段樹 分段樹雖然儲存的是區間分段的和,但是分段樹的葉子節點就是儲存的nums中每個元素的值,對應於left==right的時候 所以更新某個元素值時,遞迴地從下到上進行更新 if left == right and left == i: 更新pos的values = val 否則求重點mid盡心分割,更新左右子樹, values[pos] = 左子樹和+右子樹和 """ def update_segment_tree(pos, left, right): if left == right and left == i: self.values[pos] = val return mid = (left + right) // 2 if i <= mid: update_segment_tree(pos * 2 + 1, left, mid) else: update_segment_tree(pos * 2 + 2, mid + 1, right) self.values[pos] = self.values[pos * 2 + 1] + self.values[pos * 2 + 2] update_segment_tree(0, 0, self.nums_end) def sumRange(self, i, j): """ 求[i,j]區間和 當建立分段樹後,求[i,j]區間和就比較順暢了,將[i,j]從根節點向下遍歷並分段,不斷求得子區間的值並且sum起來即是答案 在向下遞迴求和的過程中要注意遞迴的出口,其實也就是左右區間分段的原則,要注意在每次向下遞迴的時候,比較的是i,j與當前 節點的left,right的關係來決定子分段和的值 if [i,j] [left,right ] or [left,right] [i,j] i,j在left,right以外的位置 return 0 if [i,[lef,right],j] 如果left,right 在i,j內, return values[pos] left,right內有多少返回多少 否則的話說明 i,left,..,j,..right,ij與left,right是有部分有交集的,則對當前left,right區間通過中點mid進行分割 return 左側和+右側和 """ def sum_range_segment_tree(pos, left, right): if i > right or j < left: return 0 if left >= i and right <= j: # 線段樹當前範圍[left,right]在所求區間[i,j]中則返回當前這個pos處的values return self.values[pos] mid = (left + right) // 2 #否則說明線段樹當前pos的[left,right]的範圍是d大於[i,j]的,需要分割 return sum_range_segment_tree(pos * 2 + 1, left, mid) + sum_range_segment_tree(pos * 2 + 2, mid + 1, right) return sum_range_segment_tree(0, 0, self.nums_end)