数据结构与算法----复习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)

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/459797.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自动点赞软件崛起背后的秘密!你还不知道就真的OUT了!

先来看视频 智能引流黑科技&#xff0c;ks自动点赞软件教程 在数字化的世界中&#xff0c;社交媒体已经成为了我们日常生活的一部分。点赞、评论、分享&#xff0c;这些互动方式在塑造我们的数字身份的同时&#xff0c;也推动了信息的传播。然而&#xff0c;随着自动点赞软件的…

css入门基础(二)链接伪类细节详讲

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 1.链接伪类的使用顺序规范 2.链接伪类的使用效果 3.浏览器安全策略对visited伪类造成的影响 4.visited伪类的工作原理 源码&#xff1a; index.html <!DOCTYPE html> <html lang"en"> <head&…

【算法专题--双指针算法】leetcode--283. 移动零、leetcode--1089. 复写零

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 前言1. 移动零&#xff0…

性能分析与调优(硬核分享)

前言 常看到性能测试书中说&#xff0c;性能测试不单单是性能测试工程师一个人的事儿。需要DBA 、开发人员、运维人员的配合完成。但是在不少情况下性能测试是由性能测试人员独立完成的&#xff0c;退一步就算由其它人员的协助&#xff0c;了解系统架构的的各个模块对于自身的…

2024年【天津市安全员C证】考试内容及天津市安全员C证考试报名

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 天津市安全员C证考试内容是安全生产模拟考试一点通生成的&#xff0c;天津市安全员C证证模拟考试题库是根据天津市安全员C证最新版教材汇编出天津市安全员C证仿真模拟考试。2024年【天津市安全员C证】考试内容及天津市…

为什么光学器件需要厚度

确定光学厚度的限值 光学元件的功能和性能在很大程度上受到可用光学材料的限制。制造和光学元件设计的最新发展现在拓宽了可以实现的目标。特别是&#xff0c;平面光学器件或超表面可以设计为具有大块光学元件的功能&#xff0c;但其厚度缩小到仅几百纳米。米勒现在提出了一项…

【DL经典回顾】激活函数大汇总(十三)(Sinc SwiGLU附代码和详细公式)

激活函数大汇总&#xff08;十三&#xff09;&#xff08;Sinc & SwiGLU附代码和详细公式&#xff09; 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里&#xff0c;激活函数扮演着不可…

【PTA+LeetCode】递归----代码练习

递归学习博客&#xff1a;博客地址 1.递归实现指数函数 double calc_pow( double x, int n ){//1.确定退出条件//2.找等价关系式if(n1){return x;}return x*calc_pow(x,n-1); }2.递归计算Ackermenn函数 int Ack( int m, int n ){//1.确定退出条件//2.确定关系式if(m0){return …

2.2 HTML5保留的常用标签

2.2.1 基础标签 1. 段落标签<p> 段落标签<p>和</p>用于形成一个新的段落&#xff0c;段落与段落之间默认为空一行进行分割。 2. 标题标签<h1>-<h6> HTML5使用<hn>和</hn>来标记文本中的标题&#xff0c;其中n需要替换为数字&#x…

R语言数据挖掘-关联规则挖掘(1)

一、分析目的和数据集描述 要分析的数据是美国一区域的保险费支出的历史数据。保险费用数据表的每列分别为年龄、性别、体重指数、孩子数量、是否吸烟、所在区域、保险收费。 本文的主要目的是分析在年龄、性别、体重指数、孩子数量、是否吸烟、所在区域中这些因素中&#xf…

第二十四节 Java 异常处理

什么是异常&#xff1f; 程序运行时&#xff0c;发生的不被期望的事件&#xff0c;它阻止了程序按照程序员的预期正常执行&#xff0c;这就是异常。异常发生时&#xff0c;是任程序自生自灭&#xff0c;立刻退出终止&#xff0c;还是输出错误给用户&#xff1f;或者用C语言风格…

WordPress网站启用cloudflare的CDN加速后,网站出现多重定向无法访问

这是一个使用Hostease的Linux虚拟主机的客户反馈的问题&#xff0c;Hostease的虚拟主机使用的也是cPanel面板&#xff0c;客户使用的是cPanel的softaculous安装的WordPress&#xff0c;但是在安装完成后&#xff0c;并且解析了域名之后&#xff0c;发现网站无法访问&#xff0c…

编译原理学习之-一个简单的语法制导翻译器

第二章 一个简单的语法制导翻译器 将具有代表性的程序设计语言语句翻译为三地址码&#xff08;一种中间表示形式&#xff09;&#xff0c;本章的重点是编译器的前端&#xff0c;特别是词法分析&#xff0c;语法分析和中间代码生产。 建立一个中缀算术表达式转换为后缀表达式的…

3.3 ss-sp寄存器,栈的push和pop指令

汇编语言 1. 栈 栈是一种具有特殊的访问方式的存储空间它的特殊性就在于&#xff0c;最后进入这个空间的数据&#xff0c;最先出去。即先进后出 1.1 栈的基本操作 入栈&#xff1a;入栈就是将一个新的元素放到栈顶出栈&#xff1a;出栈就是从栈顶取出一个元素栈顶的元素总是…

【计算机视觉】二、图像形成:2、几何基元和几何变换:2D变换

文章目录 一、向量和矩阵的基本运算二、几何基元和变换1、几何基元(Geometric Primitives)2、几何变换(Geometric Transformations)1. 各种变换的关系2. 变换公式3. 2D变换的层次4. python实现 一、向量和矩阵的基本运算 【计算机视觉】二、图像形成&#xff1a;1、向量和矩阵…

工业物联网平台在水务环保、暖通制冷、电力能源等行业的应用

随着科技的不断发展&#xff0c;工业物联网平台作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为推动各行业智能化转型的关键力量。在水务环保、暖通制冷、电力能源等行业&#xff0c;工业物联网平台的应用尤为广泛&#xff0c;对于提升运营效率、降低能耗、优化管理等…

【C++设计模式】UML图的介绍及其画法

文章目录 前言一、UML图的介绍1.1 UML图是什么1.2 UML图的作用 二、UML图的画法2.1 最简单的UML图2.2 继承的UML图2.3 关联关系2.4 聚合关系2.5 组合关系2.6 依赖关系 总结 前言 在软件开发过程中&#xff0c;设计模式是一种被广泛应用的方法&#xff0c;它为解决特定问题提供…

利用数据驱动的MEG分析方法提取fMRI静息态网络

摘要 静息态网络(RSN)的电生理基础仍存在争议。特别是&#xff0c;尚未确定一个能够同样有效解释所有静息态网络的原理性机制。虽然脑磁图(MEG)和脑电图(EEG)是确定RSN电生理基础的首选方法&#xff0c;但目前没有标准的RSN分析流程。本文比较了从MEG数据中提取RSNs的两种现有…

Profinet转CC-Link网关操作技巧及功能

Profinet转CC-Link网关&#xff08;XD-PNCR20&#xff09;是一款可有效连接CCLINK总线和Profinet网络的通讯网关。Profinet转CC-Link网关主要功能是将各种CCLINK总线和Profinet网络连接起来&#xff0c;实现各种总线的互联通信。 Profinet转CC-Link网关连接到Profinet总线中做…

电源常用通讯电路详解

数字电源的采样和PWM驱动电路原理&#xff0c;通过这些技术&#xff0c;数字电源可以在内部形成控制闭环。但是要实现电源的控制和管理&#xff0c;还是需要与数字控制核心建立通讯连接。本期将带领大家了解数字电源常用的通讯电路。 一、常用的通讯方式 在前面数字电源与模拟…