1. 程式人生 > >LeetCode 307 Medium 區間和查詢 分段樹 Python

LeetCode 307 Medium 區間和查詢 分段樹 Python

    演算法:分段樹 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)