「线段树」第 1 节:线段树是原始数组的一个预处理数组

  • 线段树(segment tree)又称「区间树」,是一个高级数据结构,应用的对象是「数组」;
  • 线段树是一种实现了高效的「区间查询」与「区间更新」的数据结构

前置知识:理解「前缀和」数组

  • preSum[i] 表示 nums[0..i - 1] 里全部元素的和(一个数代表了原始数组的一个前缀区间的和);

  • 前缀和数组,就是一堆前缀和,可以用于:快速计算区间和(查询区间和 );

  • 区间 [i..j] 的和: preSum[j + 1] - preSum[i]

「前缀和数组」与「线段树」都是原始数组的预处理数组

  • 有了前缀和数组,就可以把原始数组丢弃了;

  • 有了线段树(数组),也可以把原始数组丢弃了。

可以认为都是对原始数组的预处理数组,把一些需要用到的值提前计算出来,思想:空间换时间

例题:「力扣」第 303 题:区域和检索 - 数组不可变

分析:

  • 我们可以设计一个前缀和数组,在查询区间和的时候,只用 O ( 1 ) 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(N) O(N)
  • 如果我们使用线段树,就可以把时间复杂度降低到 O ( log ⁡ N ) O(\log N) O(logN)

这里要注意的是「线段树」解决的区间问题不涉及「添加」与「删除」操作。即「CURD」,只负责「U」 和 「R」。

使用「遍历」与使用「线段树」对于「区间更新」与「区间查询」操作的复杂度

遍历 线段树
区间查询 O ( N ) O(N) O(N) O ( log ⁡ N ) O(\log N) O(logN)
区间更新 O ( N ) O(N) O(N) O ( log ⁡ N ) O(\log N) O(logN)

说明:由于我们的线段树(区间树)采用平衡二叉树实现,因此 O ( log ⁡ N ) O(\log N) O(logN) 中的对数函数以 2 为底,即 O ( log ⁡ N ) = O ( log ⁡ 2 N ) O(\log N) = O(\log_2 N) O(logN)=O(log2N)

总结

线段树只回答了以下两个问题,不回答区间里有「删除」和「添加」操作的场景。

  • 区间和查询
  • 单点(区间)更新

猜你喜欢

转载自blog.csdn.net/lw_power/article/details/106965323