数据结构与算法----复习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 = valself.left = leftself.right = rightclass Solution:def searchBST(self, root: TreeNode, val: int):if not root:return Noneif val == root.val:return rootelif 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 = valself.left = leftself.right = rightclass Solution:def searchBST(self, root: TreeNode, val: int):if not root:return Noneif val == root.val:return rootelif 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 Noneif 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 = Nonereturn rootelif root.left != None and root.right == None:return root.leftelif root.left == None and root.right != None:return root.rightelse:node = root.rightwhile node.left:node = node.leftroot.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 Trueans = []def inorder(root):if not root:return inorder(root.left)ans.append(root.val)inorder(root.right)return ansans = inorder(root)for i in range(1, len(ans)):if ans[i] <= ans[i - 1]:return Falsereturn 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.leftdef next(self) -> int:node = self.stack.pop()if node.right:self.in_order(node.right)return node.valdef 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 = -1self.right = -1self.val = valself.lazy_tag = Noneclass SegmentTree:def __init__(self, nums, function):     # function 根据题目要求确定该节点的值self.size = len(nums)self.tree = [SegmentTreeNode() for _ in range(4 * self.size)]self.nums = numsself.function = functionif self.size > 0:self.__build(0, 0, self.size -1)def __build(self, index, left, right):self.tree[index].left = leftself.tree[index].right = rightif left == right:self.tree[index].val = self.nums[left]returnmid = left + (right - left) // 2    # mid 记录节点存储的区间长度left_index = index * 2 + 1right_index = index * 2 + 2self.__build(left_index, left, mid)self.__build(right_index, mid + 1, right)self.__pushup(index)def __pushup(self, index):left_index = index * 2 + 1right_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] = valself.__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 = valreturnmid = left + (right - left) // 2left_index = index * 2 + 1right_index = index * 2 + 2if 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].valif right < q_left or left > q_right:return 0self.__pushdown(index)      # 在访问节点时调用mid = left + (right - left) // 2left_index = index * 2 + 1right_index = index * 2 + 2res_left = 0res_right = 0if 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 = -1self.right = -1self.val = valself.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 * valself.tree[index].lazy_tag = valreturnif right < q_left or left > q_right:return 0self.__pushdown(index)mid = left + (right - left) // 2left_index = index * 2 + 1right_index = index * 2 + 2if 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_tagdef __pushdown(self, index):lazy_tag = self.tree[index].lazy_tagif not lazy_tag:returnleft_index = index * + 1right_index = index * 2 + 2self.tree[left_index].lazy_tag = lazy_tagleft_size = self.tree[left_index].right - self.tree[left_index].left + 1self.tree[left_index].val = lazy_tag * left_sizeself.tree[right_index].lazy_tag = lazy_tagright_size = self.tree[right_index].right - self.tree[right_index].left + 1self.tree[right_index].val = lazy_tag * right_sizeself.tree[index].lazy_tag = None

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java组合模式揭秘:如何构建可扩展的树形结构

组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构以表示整体/部分层次结构。组合模式使得客户端可以统一对待单个对象和组合对象&#xff0c;从而使得客户端可以处理更复杂的结构。 组合模式的主要组成部分包括&…

Unity开发一个FPS游戏之二

在之前的文章中,我介绍了如何开发一个FPS游戏,添加一个第一人称的主角,并设置武器。现在我将继续完善这个游戏,打算添加敌人,实现其智能寻找玩家并进行对抗。完成的效果如下: fps_enemy_demo 下载资源 首先是设计敌人,我们可以在网上找到一些好的免费素材,例如在Unity…

SD-WAN专线解决跨国业务应用和系统访问慢问题

在全球化背景下&#xff0c;跨国企业面临着日益增长的业务应用和系统访问需求。然而&#xff0c;由于地理位置和网络结构等因素的限制&#xff0c;跨国业务应用和系统访问常常受到网络连接速度慢的困扰。SD-WAN&#xff08;软件定义广域网&#xff09;专线作为一种新兴的网络技…

git小白入门

git是什么 Git是一种流行的版本控制系统&#xff0c;被广泛用于软件开发中来跟踪和管理代码的变化。它是由Linus Torvalds在2005年创建的&#xff0c;最初的目的是为了更高效地管理Linux内核的开发。Git使得多人在同一个项目上工作变得更加简单&#xff0c;可以轻松合并不同开…

Filter实现请求日志记录

将锁有得外部访问都记录在日志文件里面&#xff0c;设计这个功能是为了&#xff08;为什么&#xff09;&#xff1a; 1. 在不引入Promentheus进行接口监控时&#xff0c;基于日志文件就可以实现整个项目得监控。 2. 当出现问题时&#xff0c;可以基于此进行流量重放。 效果如…

Android 深入Http(2)加密与编码

可以对二进制数据&#xff08;比如图片、视频&#xff09; 经典算法&#xff1a; DES&#xff08;密钥短被弃用了&#xff09; AES &#xff08;密钥很长 很顶&#xff09; 速度快&#xff0c;效率高 IDEA 3DES&#xff08;三重DES&#xff0c;听起来就很慢和重 &#xf…

详解IPD流程之任务书(Charter)

IPD体系是一种全新的产品研发管理模式&#xff0c;它将研发合格产品整个过程分为确保开发做正确的事和如何正确地做事两个阶段。 确保开发做正确的事是指在产品进入研发之初就清晰地定义出有竞争力的产品&#xff0c;核心是确保产品能够对准客户需求&#xff0c;能够给客户带来…

【C++】手撕AVL树

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;能直接手撕AVL树。 > 毒鸡汤&#xff1a;放弃自…

第二十四节 Java 异常处理

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

Vue.js+SpringBoot开发天沐瑜伽馆管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 瑜伽课程模块2.3 课程预约模块2.4 系统公告模块2.5 课程评价模块2.6 瑜伽器械模块 三、系统设计3.1 实体类设计3.1.1 瑜伽课程3.1.2 瑜伽课程预约3.1.3 系统公告3.1.4 瑜伽课程评价 3.2 数据库设计3.2.…

使用opencv进行图片分析

opencv学习 一、配置环境并打开编译器 配置opencv在你的任意一个盘里创建一个专属于opencv的文件夹便于学习与整理 打开控制台winr输入cmd&#xff0c;进入后输入conda activate opencv&#xff0c;进入环境以后进入你所设置的opencv文件的盘&#xff0c;我的是D盘&#xff0…