「線段樹」第 1 節:線段樹是原始數組的一個預處理數組

  • 線段樹(segment tree)又稱「區間樹」,是一個高級數據結構,應用的對象是「數組」;
  • 線段樹是一種實現了高效的「區間查詢」與「區間更新」的數據結構

前置知識:理解「前綴和」數組

  • preSum[i] 表示 nums[0..i - 1] 裏全部元素的和(一個數代表了原始數組的一個前綴區間的和);

  • 前綴和數組,就是一堆前綴和,可以用於:快速計算區間和(查詢區間和 );

  • 區間 [i..j] 的和: preSum[j + 1] - preSum[i]

「前綴和數組」與「線段樹」都是原始數組的預處理數組

  • 有了前綴和數組,就可以把原始數組丟棄了;

  • 有了線段樹(數組),也可以把原始數組丟棄了。

可以認爲都是對原始數組的預處理數組,把一些需要用到的值提前計算出來,思想:空間換時間

例題:「力扣」第 303 題:區域和檢索 - 數組不可變

分析:

  • 我們可以設計一個前綴和數組,在查詢區間和的時候,只用 O(1)O(1) 時間複雜度,不過在數組元素有頻繁更新的時候,會導致性能下降,即這種方式不適用於「力扣」第 307 題;
  • 缺點:前綴和數組,在有更新需求的前提下,不能高效地工作

Java 代碼:

public class NumArray {

    // 前綴和思想:注意:有一個單位的偏移

    private int[] preSum;

    public NumArray(int[] nums) {
        int len = nums.length;
        preSum = new int[len + 1];
        for (int i = 0; i < len; i++) {
            preSum[i + 1] = preSum[i] + nums[i];
        }
    }

    public int sumRange(int i, int j) {
        return preSum[j + 1] - preSum[i];
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 4};
        NumArray numArray = new NumArray(nums);
        int result = numArray.sumRange(2, 3);
        System.out.println(result);
    }
}


/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray obj = new NumArray(nums);
 * int param_1 = obj.sumRange(i,j);
 */

Python 代碼:

from typing import List


class NumArray:

    def __init__(self, nums: List[int]):

        """
        :type nums: List[int]
        """
        self.size = len(nums)
        if self.size == 0:
            return

        self.pre_sum = [0 for _ in range(self.size + 1)]
        self.pre_sum[0] = 0
        for i in range(self.size):
            self.pre_sum[i + 1] = self.pre_sum[i] + nums[i]

    def sumRange(self, i: int, j: int) -> int:
        if self.size > 0:
            return self.pre_sum[j + 1] - self.pre_sum[i]
        return 0

# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(i,j)

「力扣」第 307 題:區域和檢索 - 數組可修改

分析:

  • 如果我們不使用任何數據結構,每次求「區間和」,都會遍歷這個區間裏的所有元素。如果區間裏包含的元素很多,並且查詢次數很頻繁,時間複雜度是 O(N)O(N)
  • 如果使用前綴和,更新操作的時間複雜度是 O(N)O(N)
  • 如果我們使用線段樹,就可以把時間複雜度降低到 O(logN)O(\log N)

這裏要注意的是「線段樹」解決的區間問題不涉及「添加」與「刪除」操作。即「CURD」,只負責「U」 和 「R」。

使用「遍歷」與使用「線段樹」對於「區間更新」與「區間查詢」操作的複雜度

遍歷 線段樹
區間查詢 O(N)O(N) O(logN)O(\log N)
區間更新 O(N)O(N) O(logN)O(\log N)

說明:由於我們的線段樹(區間樹)採用平衡二叉樹實現,因此 O(logN)O(\log N) 中的對數函數以 2 爲底,即 O(logN)=O(log2N)O(\log N) = O(\log_2 N)

總結

線段樹只回答了以下兩個問題,不回答區間裏有「刪除」和「添加」操作的場景。

  • 區間和查詢
  • 單點(區間)更新
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章