数据结构与算法----复习Part 15 (二叉搜索树与线段树)

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

目录

一,二叉搜索树(Binary Search Tree)

二叉搜索树的查找

二叉搜索树的插入

二叉搜索树的创建

二叉搜索树的删除

二,线段树(Segment Tree):

线段树的构建

线段树的单点更新

线段树的区间查询

线段树的区间更新


  

一,二叉搜索树(Binary Search Tree)

        如果任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;

        如果任意节点的右子树不为空,则右子树上所有结点的值均大于它的根节点的值;

        任意节点的左子树、右子树均为二叉搜索树;

        根据二叉搜索树,即左子树的节点值 < 根节点值 < 右子树的节点值 这一特性,如果以中序遍历的方式遍历整个二叉树,则会得到一个递增序列。

二叉搜索树的查找

        在二叉搜索树中查找值为 val 的节点:

        如果二叉搜索树为空,则返回 None;

        如果二叉搜索树不为空,则将要查找的值 val 与 root.val 比较:

                如果 val == root.val ,则查找成功,返回找到的节点;

                如果 val < root.val ,则递归查找左子树;

                如果 val > root.val ,则递归查找右子树。

class TreeNode:
    def __init__(self, val = 0, left = None, right = None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def searchBST(self, root: TreeNode, val: int):
        if not root:
            return None

        if val == root.val:
            return root
        elif val < root.val:
            return self.searchBST(root.left, val)
        else:
            return self.searchBST(root.right, val)
        

二叉搜索树的插入

        在二叉搜索树中插入一个值为 val 的节点:

        如果二叉搜索树为空,则创建一个值为 val 的节点,并作为根节点;

        如果二叉搜索树不为空,则将插入的值 val 与二叉搜索树根节点的值 root.val 比较:

                如果 val < root.val ,则递归将值插入左子树;

                如果 val > root.val ,则递归将值插入右子树。

PS:二叉搜索树不存在重复节点,否则将违反定义。如果 val 已经存在,则直接返回。

    def insertIntoBST(self, root: TreeNode, val: int):
        if root == None:
            return TreeNode(val)
        
        if val < root.val:
            root.left = self.insertIntoBST(root.left, val)
        if val > root.val:
            root.right = self.insertIntoBST(root.right, val)
        return root

二叉搜索树的创建

        根据数组序列中的元素值,建立一棵二叉搜索树:

        初始化二叉搜索树为空树;

        遍历数组元素,将数组元素 nums[i] 依次插入到二叉搜索树中;

        将数组中全部元素值插入到二叉搜索树中后,返回二叉搜索树的根节点。

class TreeNode:
    def __init__(self, val = 0, left = None, right = None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def searchBST(self, root: TreeNode, val: int):
        if not root:
            return None

        if val == root.val:
            return root
        elif val < root.val:
            return self.searchBST(root.left, val)
        else:
            return self.searchBST(root.right, val)

    def buildBST(self, nums):
        root = TreeNode(nums[0])
        for num in nums[1:]:
            self.insertIntoBST(root, num)
        return root

二叉搜索树的删除

        在二叉搜素树种删除值为 val 的节点:

        删除节点并不困难,难点在于保持删除后的仍然是二叉搜索树,可以分为三种情况

        1,被删除节点的左子树为空:

                则令其右子树代替被删除节点的位置;

        2,被删除节点的右子树为空:

                则令其左子树代替被删除节点的位置;

        3,被删除节点的左右子树都不为空:

                考虑二叉搜索树的特点:中序遍历结果是一个升序排列,那么将被删除节点的中序遍历

        前驱或后继替换当前节点即可,这样保证了新树的中序遍历仍为升序。

                

        观察 C 节点我们可以发现,中序遍历的前驱是其左子树的最右侧叶子节点,而中序遍历的后继是其右子树最左侧的叶子节点。

        如果当前节点为空,则返回当前节点;

        如果当前节点的值大于 val,则递归去左子树搜索并删除,因为左子树可能有变化,所以 root.left 也要跟着递归更新;

        如果当前节点的值小于 val,则递归去右子树搜索并删除,root.right 也要递归更新;

        如果当前节点的值等于 val:

                如果当前节点的左子树为空,则删除之后,右子树代替当前节点位置,返回右子树;

                如果当前节点的右子树为空,则删除之后,左子树代替当前节点位置,返回左子树;

                如果当前节点左右子树后不为空,则将左子树转移到右子树最左侧叶子节点的位置,然

        后右子树代替当前节点的位置。

    def deleteNode(self, root: TreeNode, val: int):
        if root == None:
            return None
        
        if root.val > val:
            root.left = self.deleteNode(root.left, val)
        elif root.val < val:
            root.right = self.deleteNode(root.right, val)
        else:
            if root.left == None and root.right == None:
                root = None
                return root
            elif root.left != None and root.right == None:
                return root.left
            elif root.left == None and root.right != None:
                return root.right
            else:
                node = root.right
                while node.left:
                    node = node.left
                root.val = node.val
                # 删除右子树的最小值
                root.right = self.deleteNode(root.right, node.val)
        return root

98. 验证二叉搜索树

class Solution:
    def isValidBST(self, root: Optional[TreeNode]) -> bool:
        if not root:
            return True
        ans = []
        def inorder(root):
            if not root:
                return 
            inorder(root.left)
            ans.append(root.val)
            inorder(root.right)
            return ans
        ans = inorder(root)
        for i in range(1, len(ans)):
            if ans[i] <= ans[i - 1]:
                return False
        return True

173. 二叉搜索树迭代器

class BSTIterator:

    def __init__(self, root: TreeNode):
        self.stack = []
        self.in_order(root)

    def in_order(self, node):
        while node:
            self.stack.append(node)
            node = node.left

    def next(self) -> int:
        node = self.stack.pop()
        if node.right:
            self.in_order(node.right)
        return node.val

    def hasNext(self) -> bool:
        return len(self.stack) != 0

        用显式栈维护二叉搜索树,要注意节点的访问顺序。 

二,线段树(Segment Tree):

        一种基于分治思想的二叉树,用于在区间上进行信息统计,它的每一个节点都对应一个区间[left, right],left,right 通常是整数。每一个叶子节点表示了一个单位区间(长度为 1 ),叶子节点对应区间上 left = right。每一个非叶子节点 [left, right] 的左叶子节点都为 [left, (left + right) / 2],右叶子节点表示的区间都为 [(left + right) / 2 + 1, right]。

        线段树是一棵平衡二叉树(高度差不超过 1),树上的每个节点维护一个区间。根节点维护的是整个区间,每个节点维护的是父节点区间二等分之后的其中一个子区间。当有 n 个元素时,对区间操作可以在 O(logn) 的时间复杂度内完成。

区间为 [0, 7]的线段树

线段树的特点:

        线段树的每个节点都代表一个区间;

        线段树具有唯一的根节点,代表的区间时整个统计范围;

        线段树的每个叶子节点都代表一个长度为 1 的区间[x, x];

        对于每个内部节点[left, right],它的左子节点是 [left, mid],右子节点是 [mid + 1, right]。其中 mid = (left + right) / 2。

线段树的构建

        由于线段树近乎是完全二叉树,所以使用 顺序存储结构。

        我们可以采用与完全二叉树类似的编号方法来对线段树进行编号:

                根节点的编号为 0;

                如果某二叉树节点(非叶子节点)的下标为 i,那么其左孩子的下标为 2 * i + 1,右孩子

        节点的下标为 2 * i + 2;

                如果某二叉树节点(非根节点)的下标为 i,那么其父节点的下标为 (i - 1) // 2。

        上图可知,下标为 i 节点的子节点下标为 2 * i + 1 和 2 * i + 2,所以线段树适合采用递归的方法来创建:

        如果是叶子节点(left = right),则节点的值就是对应位置的元素值;

        如果是非叶子节点,则递归创建左右子树;

        节点的区间值(区间和、区间最大值、区间最小值)等于该节点左右子节点元素值的对应计算结果。

class SegmentTreeNode:
    def __init__(self, val = 0):
        self.left = -1
        self.right = -1
        self.val = val
        self.lazy_tag = None

class SegmentTree:
    def __init__(self, nums, function):     # function 根据题目要求确定该节点的值
        self.size = len(nums)
        self.tree = [SegmentTreeNode() for _ in range(4 * self.size)]
        self.nums = nums
        self.function = function
        if self.size > 0:
            self.__build(0, 0, self.size -1)

    def __build(self, index, left, right):
        self.tree[index].left = left
        self.tree[index].right = right
        if left == right:
            self.tree[index].val = self.nums[left]
            return

        mid = left + (right - left) // 2    # mid 记录节点存储的区间长度
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        self.__build(left_index, left, mid)
        self.__build(right_index, mid + 1, right)
        self.__pushup(index)

    def __pushup(self, index):
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        # 按照节点值的计算方式,更新当前节点的值
        # 因为是递归地构造线段树,所以叶子节点已经构造好
        self.tree[index].val = self.function(self.tree[left_index].val, self.tree[right_index].val)

线段树的单点更新

        修改一个元素的值,例如将 nums[ i ] 修改为 val:

                如果是叶子节点,满足 left = right,则更新该节点的值;

                如果是非叶子节点,则判断应该在左子树还是在右子树中更新;

                在对应的子树中更新节点值;

                左右子树更新返回之后,向上更新节点的区间值( function 中实现的 区间和、区间最大

        值或是区间最小值等)。

    def update_point(self, i, val):
        self.nums[i] = val
        self.__update_point(i, val, 0, 0, self.size - 1)

    def __update_point(self, i, val, index, left, right):
        if self.tree[index].left == self.tree[index].right:
            self.tree[index].val = val
            return

        mid = left + (right - left) // 2
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        if i <= mid:
            self.__update_point(i, val, left_index, left, mid)
        else:
            self.__update_point(i, val, right_index, mid + 1, right)
        
        self.__pushup(index)

线段树的区间查询

        查询一个区间为 [q_left, q_right] 的区间值:

                如果区间 [q_left, q_right] 完全覆盖了当前节点所在的区间 [left, right] ,即 left >= q_leftt         并且 right <= q_right,则返回该节点的区间值,即该节点的 val;

                如果区间 [q_left, q_right] 与当前节点毫无关系,则返回 0;

                如果区间 [q_left, q_right] 与当前节点所在区间有交集,则:

                        如果区间 [q_left, q_right] 与左子树所在区间 [left, mid] 有交集,则在当前节点的左

                子树中进行查询,并保存查询结果 res_left;

                        如果区间 [q_left, q_right] 与右子树所在区间 [mid + 1, right] 有交集,则在当前节

                点的右子树中进行查询,并保存查询结果 res_right;

                        最后返回左右子树元素区间值的聚合计算结果。

    def query_interval(self, q_left, q_right):
        return self.__query_interval(q_left, q_right, 0, 0, self.size - 1)

    def __query_interval(self, q_left, q_right, index, left, right):
        if left >= q_left and right <= q_right:
            return self.tree[index].val
        if right < q_left or left > q_right:
            return 0

        self.__pushdown(index)      # 在访问节点时调用

        mid = left + (right - left) // 2
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        res_left = 0
        res_right = 0
        if q_left <= mid:
            res_left = self.__query_interval(q_left, q_right, left_index, left, mid)
        if q_right > mid:
            res_right = self.__query_interval(q_left, q_right, right_index, mid + 1, right)
        
        return self.function(res_left, res_right)

        其中 self.__pushdown() 函数的作用在线段树的区间更新中

线段树的区间更新

        对 [q_left, q_right] 区间进行更新,如将 [q_left, q_right] 区间内所有元素更新为 val:

        在区间更新操作中,如果某个节点区间P [left, right] 被修改区间 [q_left, q_right] 完全覆盖,则以该节点 P 为根的整棵子树中的节点,都要修改。但是如果后序的查找操作中,没有用到修改后的节点 P 作为候选答案,那么更新以节点 P 为根的子树这一操作就是徒劳的。

        因此在节点中

class SegmentTreeNode:
    def __init__(self, val = 0):
        self.left = -1
        self.right = -1
        self.val = val
        self.lazy_tag = None

        最后一行增加了一个【延迟标记】,意为 该区间曾经被修改为 val,但其子节点的值尚未更新。也就是说,将区间节点 P 子节点的更新操作延迟到 在后续操作中递归进入子节点 时再执行。

使用 延迟标记 的区间更新步骤为:

        如果区间 [q_left, q_right] 完全覆盖了当前节点所在区间 [left, right],则更新当前节点所在区间的值,并将当前节点的延迟标记为区间值;

        如果区间 [q_left, q_right] 与当前节点所在区间毫无关系,则直接返回;

        如果区间 [q_left, q_right] 与当前节点所在区间有交集:

                如果当前节点使用了【延迟标记】,即延迟标记不为 None,则将当前区间的更新操作应

        用到该节点的子节点上,即向下更新;

                如果区间 [q_left, q_right] 与左子节点所在区间有交集,则在左子树中更新区间值;

                如果区间 [q_left, q_right] 与右子节点所在区间有交集,则在右子树中更新区间值;

                左右子树更新返回后,向上更新区间值。

PS:向下更新

        更新左子节点和左子节点的 lazy_tag = val;

        更新右子节点和右子节点的 lazy_tag = val;

        将当前节点的 lazy_tag = None;

        

    def update_interval(self, q_left, q_right, val):
        self.__update_interval(q_left, q_right, val, 0, 0, self.size - 1)

    def __update_interval(self, q_left, q_right, val, index, left, right):

        if left >= q_left and right <= q_right:
            interval_size = (right - left + 1)
            self.tree[index].val = interval_size * val
            self.tree[index].lazy_tag = val
            return
        if right < q_left or left > q_right:
            return 0

        self.__pushdown(index)

        mid = left + (right - left) // 2
        left_index = index * 2 + 1
        right_index = index * 2 + 2
        if q_left <= mid:
            self.__update_interval(q_left, q_right, val, left_index, left, mid)
        if q_right > mid:
            self.__update_interval(q_left, q_right, val, right_index, mid + 1, right)

        self.__pushup(index)

    # 向下更新下标为 index 的节点所在区间的左右子节点的值个 lazy_tag
    def __pushdown(self, index):
        lazy_tag = self.tree[index].lazy_tag
        if not lazy_tag:
            return

        left_index = index * + 1
        right_index = index * 2 + 2

        self.tree[left_index].lazy_tag = lazy_tag
        left_size = self.tree[left_index].right - self.tree[left_index].left + 1
        self.tree[left_index].val = lazy_tag * left_size
        
        self.tree[right_index].lazy_tag = lazy_tag
        right_size = self.tree[right_index].right - self.tree[right_index].left + 1
        self.tree[right_index].val = lazy_tag * right_size
        
        self.tree[index].lazy_tag = None

算法通关手册(LeetCode) | 算法通关手册(LeetCode)

原文内容在这里,如有侵权,请联系我删除。

  • 23
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值