[leetcode刷题笔记]线段树Segment Tree

作者: KeyLiu7 | 来源:发表于2020-07-10 14:23 被阅读0次

在做两道区间检索题中接触到了线段树的概念,本文将其进行整理,文末几篇参考对线段树做了系统的介绍,结合两道leetcode题,对线段树的创建、查找、更新有了更深地掌握。文中不足,还望多多指正。

区域和检索 - 数组不可变

给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

示例:

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

线段树(Segment Tree)也是一棵树,只不过元素的值代表一个区间。常用区间的统计操作,比如一个区间的最大值(max),最小值(min),和(sum)等等,线段树是一个平衡二叉树,但不一定是完全二叉树。

在本题中,数组nums=[-2, 0, 3, -5, 2, -1]构建一棵线段树如下,节点为区间和(sum)

根节点就是 0~lenght-1 的和,根节点的左右子树平分根节点的区间,然后依次类推,直到只有一个元素不能划分为止,该元素也就是二叉树的叶子节点。

求线段树的区间统计,时间复杂度和二叉树的高度有关系,和元素的个数没关系,它的时间复杂度为 O(log n)。

如果我们用数组来存储线段树的话,我们大致需要开辟多大的数组空间呢?
h层的满二叉树总共有 2^h-1 个节点,第h-1层有2^(h-1)个节点,它们大概是两倍的关系。也就是说对于满二叉树 最后一层的节点数乘以2 大致就是整棵树的节点数。但是线段树并不一定是满二叉树,但是一定是平衡二叉树,所以需要多冗余一层。也就是 乘以4 就足以盛放所有的节点数,但是会浪费一定的内存空间。

class SegmentTree:
    def __init__(self, data:List[int]): 
        '''
        data:传入的数组
        merge:处理的业务逻辑,例如求和/最大值/最小值,lambda表达式
        '''
        self.data = data
        self.n = len(data)
        #  申请4倍data长度的空间来存线段树节点
        self.tree = [None] * (4 * self.n) # 索引i的左孩子索引为2i+1,右孩子为2i+2
        if self.n:
            self.build(0, 0, self.n-1)

    def querySum(self, ql:int, qr:int) -> int:
        '''
        返回区间[ql,..,qr]的值
        '''
        return self.query(0, 0, self.n-1, ql, qr)

    def build(self, tree_index:int, l:int, r:int):
        '''
        递归创建线段树
        tree_index : 线段树节点在数组中位置
        l, r : 该节点表示的区间的左,右边界
        '''
        if l == r:
            self.tree[tree_index] = self.data[l]
            return
        mid = (l+r) // 2 # 区间中点,对应左孩子区间结束,右孩子区间开头
        left, right = 2 * tree_index + 1, 2 * tree_index + 2 # tree_index的左右子树索引
        self.build(left, l, mid)
        self.build(right, mid+1, r)
        self.tree[tree_index] = self.tree[left] + self.tree[right]

查找查找分为四种情况

  • 查询的区间在刚好就是当前节点表示区间
    查询结果即为当前节点值

  • 查询区间全在左子树

  • 查询区间全在右子树

  • 查询区间部分在左子树,部分在右子树
    需到左右子树分别查询,并将查询结果求和(本题构造求和的线段树)

     def query(self, tree_index:int, l:int, r:int, ql:int, qr:int) -> int:
         '''
         递归查询区间[ql,..,qr]的值
         tree_index : 某个根节点的索引
         l, r : 该节点表示的区间的左右边界
         ql, qr: 待查询区间的左右边界
         '''
         if l == ql and r == qr:
             return self.tree[tree_index]
    
         # 区间中点,对应左孩子区间结束,右孩子区间开头
         mid = (l+r) // 2 
         left, right = tree_index * 2 + 1, tree_index * 2 + 2
         if qr <= mid:
             # 查询区间全在左子树
             return self.query(left, l, mid, ql, qr)
         elif ql > mid:
             # 查询区间全在右子树
             return self.query(right, mid+1, r, ql, qr)
    
         # 查询区间一部分在左子树一部分在右子树
         return self.query(left, l, mid, ql, mid) + self.query(right, mid+1, r, mid+1, qr)
    

构建好线段树后,本题操作

class NumArray:

    def __init__(self, nums: List[int]):
        self.segment_tree = SegmentTree(nums)
    

    def sumRange(self, i: int, j: int) -> int:
        return self.segment_tree. querySum(i, j)

区域和检索 - 数组可修改

给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

update(i, val) 函数可以通过将下标为 i 的数值更新为 val,从而对数列进行修改。

示例:

Given nums = [1, 3, 5]

sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8

说明:

数组仅可以在 update 函数下进行修改。
你可以假设 update 函数与 sumRange 函数的调用次数是均匀分布的。

相比上一题增加了update操作,其更新操作需找到相应的叶子结点,将其值更新,然后将包括其大区间的值更新。

def update(self, tree_index, l, r, index):
    '''
    tree_index:某个根节点索引
    l, r : 此根节点代表区间的左右边界
    index : 更新的值的索引
    '''
    if l == r==index :
        self.tree[tree_index] = self.data[index]
        return
    mid = (l+r)//2
    left, right = 2 * tree_index + 1, 2 * tree_index + 2
    if index > mid:
        # 要更新的区间在右子树
        self.update(right, mid+1, r, index)
    else:
        # 要更新的区间在左子树index<=mid
        self.update(left, l, mid, index)

    self.tree[tree_index] = self.tree[left] + self.tree[right]

def updateSum(self,index:int,value:int):
    self.data[index] = value
    self.update(0, 0, self.n-1, index)

Reference

1.数据结构与算法(十)线段树(Segment Tree)入门
2.线段树SegmentTree-数组实现

相关文章

  • [leetcode刷题笔记]线段树Segment Tree

    在做两道区间检索题中接触到了线段树的概念,本文将其进行整理,文末几篇参考对线段树做了系统的介绍,结合两道leetc...

  • 线段树(Segment Tree)

    线段树本质上还是二叉树, 不同的是它的每个节点记录了一段区间的信息.所以很多算法的实现还是大量的递归, 二分的思路...

  • Segment Tree线段树

    线段树的作用范围:区间单点更新和覆盖,区间多点更新和覆盖。时间复杂度:O(logN)原理图: 对于一个长度为N的数...

  • 线段树(segment tree),看这一篇就够了

    定义 线段树(segment tree),顾名思义, 是用来存放给定区间(segment, or interval...

  • LeetCode 分类刷题 —— Segment Tree

    Segment Tree 的 Tips: 线段数的经典数组实现写法。将合并两个节点 pushUp 逻辑抽象出来了,...

  • 10.线段树(比较高级的数据结构)

    一、线段树(区间树)的概念 Segment Tree;线段树属于高级数据结构,经常出现在算法竞赛中为什么要使用线段...

  • 数据结构之线段树

    什么是线段树 线段树(Segment Tree)也叫区间树,其本质上是一种二分搜索树[https://www.ji...

  • 9-玩转数据结构-线段树

    上一章我们介绍了堆,这一章我们介绍一种新的树结构,线段树(区间树) Segment Tree 为什么使用线段树? ...

  • Data Structure_树

    线段树Segment Tree 对于有一类问题,时常关注的是一个区间或者是一个线段,那么就可以使用线段树来解决。比...

  • 基础九 线段树Segment Tree

    线段树功能: O(logN) 找到某区间的 最大最小值 元素个数 区间和 O(1) 得到全部区间的 最大最小值 元...

网友评论

    本文标题:[leetcode刷题笔记]线段树Segment Tree

    本文链接:https://www.haomeiwen.com/subject/gevqcktx.html