数据结构与算法----复习Part 14 (树与二叉树)

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

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

目录

一,树(Tree)

树的相关术语

节点间关系

树的其他术语

二,二叉树(Binary Tree)

满二叉树(Full Binary Tree)

完全二叉树(Complete Binary Tree)

二叉搜索树(Binary Search Tree)

平衡二叉搜索树(Balanced Binary Tree)

三,二叉树的存储结构

顺序存储结构

链式存储结构

四,二叉树的遍历

二叉树的前序遍历

二叉树的中序遍历

二叉树的后序遍历

二叉树的层序遍历

五,二叉树的还原

从前序和中序序列构造二叉树:

从中序和后序序列构造二叉树

从前序和后序序列构造二叉树


 一,树(Tree)

        由 n >= 0 个节点与节点之间的关系组成的有限集合。 n = 0 时称空树,n > 0 时称非空树。

        树 具有以下特点:

                有且仅有一个节点没有前驱节点,该节点称为树的【根节点(Root)】;

                除了根节点之外,每个节点有且仅有一个直接前驱节点;

                包括根节点在内,每个节点可以有多个后继节点(一对多的关系);

                当 n > 1 时,除了根节点之外的其他节点与其后继节点组成的也是一棵树,称为根的

        【子树(Sub Tree)】;

树的相关术语

        

树的节点:由一个数据元素和若干个指向其子树的分支组成(节点 加 指向子树的箭头);

节点的度:一个节点所含有的子树个数;

叶子节点:度为 0 的节点,如图中的 C、F、H、I、G、K;

分支节点:度不为 0 的节点,如图中的 A、B、D、E、G;

数的度:树中节点的最大度数,如图中树的度为 3;

节点间关系

子节点:一个节点含有的【子树的根节点】称为该节点的子节点;

父节点:如果一个节点含有子节点,则称该节点为其子节点的父节点;

兄弟节点:具有相同父节点的节点互称为兄弟节点;

树的其他术语

节点的层次:从根节点开始定义,根为第 1 层,根的子节点为第 2 层,以此类推;

树的深度(高度):所有节点中最大的层数;

堂兄弟节点:父节点在同一层的节点互为堂兄弟;

路径:树中两个节点之间所经过的节点序列。如图中 E 到 G 的路径为 E-B-A-D-G;

路径长度:两个节点之间路径上经过的边数。如图中 E 到 G 的路径长度为 4;

节点的祖先:从该节点到根节点所经过的所有节点,被称为该节点的祖先。如图中 H 的祖先为 E、B、A;

节点的子孙:节点的子树中所有节点被称为该节点的子孙。例如图中 D 的子孙为 F、G、K

有序树:节点的各个子树从左至右有序,不能呼唤位置;

无序树:节点的各个子树可互换位置。

二,二叉树(Binary Tree)

        树中各个节点的度不大于 2 个的有序树,称为二叉树。通常树中的分支节点被称为【左子树】或【右子树】。二叉树的分支具有左右次序,不能随意互换位置。

        二叉树可以用递归的方式来定义,即二叉树满足以下两个要求之一:

                空树:二叉树是一个空树;

                非空树:二叉树是有一个根节点和两棵互不相交的子树,分别称为根节点的左子树、右

        子树组成的非空树,并且其本身都是二叉树。

        二叉树是种特殊的树,它最多有两个子树,分别为左子树和右子树,并且两个子树是有序的,不可以互换。也就是说,在二叉树种不存在度大于 2 的节点。

        二叉树在逻辑上可以分为 5 种基本形态

满二叉树(Full Binary Tree)

        如果所有分支节点都存在左子树和右子树,并且所有叶子节点都在同一层上,则称该二叉树为满二叉树。

        满二叉树满足:

                叶子节点只出现在最下面一层;

                非叶子节点的度一定为 2 ;

                在同等深度的二叉树中,满二叉树的节点个数最多,叶子节点个数最多。

        如果我们对满二叉树的节点进行编号。根节点编号为 1,然后按照层次依次向下,每一层从左到右的顺序进行编号,则深度为 k 的满二叉树最后一个节点的编号为 2^k - 1。

完全二叉树(Complete Binary Tree)

        如果叶子节点只能出现在最下面两层,并且最下层的叶子节点都依次排列在该层最左边的位置上,称为完全二叉树。

        完全二叉树满足:

                叶子节点只能出现在最下面两层;

                最下层的叶子节点一定集中在该层最左边的位置上;

                倒数第二层如果有叶子节点,则一定集中在右边位置上;

                如果节点的度为一,则该节点只有做孩子节点;

                

二叉搜索树(Binary Search Tree)

        也叫二叉查找树,是指一棵空树或具有以下性质的二叉树:

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

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

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

平衡二叉搜索树(Balanced Binary Tree)

        一种结构平衡的二叉搜索树。即叶节点的高度差绝对值不超过 1,并且左右两个子树都是一棵平衡二叉搜索树。

三,二叉树的存储结构

        二叉树的存储结构分为两种:【顺序存储结构】和【链式存储结构】。

顺序存储结构

        其实,堆排序、优先队列中的二叉堆结构,采用的就是二叉树的顺序存储结构。

        二叉树的顺序存储结构使用一维数组在存储二叉树中的节点,节点存储位置则采用完全二叉树的节点层次编号,按照层次从上到下,每一层从左到右的顺序依次存放二叉树的数据元素。在进行顺序存储时,如果对应的二叉树节点不存在,则设置为空节点。

        可以看出节点之间的逻辑关系:

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

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

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

        对于完全二叉树(尤其是满二叉树)来说,采用顺序存储结构比较合适,它能充分利用存储空间,而对于一般二叉树,如果需要设置很多的【空节点】,则采用顺序存储结构就会浪费很多存储空间,并且由于顺序存储结构固有的一些缺陷,会使得二叉树的插入,删除等操作不方便,效率也比较低,对于二叉树来说,当数的形态和大小经常发生动态变化时,更适合采用链式存储结构。

链式存储结构

        二叉树采用链式存储结构时,每个节点包含一个用于数据域 val ,存储节点信息,还包含两个指针域 left 和 right ,分别指向左右两个孩子节点,当左孩子或右孩子不存在,相应指针域为空。

对应代码:

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

        二叉树的链表存储结构具有灵活、方便的特点。节点的最大数目只受系统最大可存储空间的限制。一般情况下,二叉树的链表存储结构比顺序存储结构更省空间,而对于二叉树实施相关操作也很方便,因此,一般使用链式存储结构来存储二叉树。

四,二叉树的遍历

        从根节点出发,按照某种次序依次访问二叉树中的所有节点,使得每个节点被访问依次且仅被访问一次。

        使用深度优先的方式遍历二叉树,分为二叉树的前、中、后序遍历。

        使用广度优先遍历二叉树,则是二叉树的层序遍历。

二叉树的前序遍历

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

        如果二叉树不为空,则:

                访问根节点;

                以前序遍历的方式遍历根节点的左子树;

                以前序遍历的方式遍历根节点的右子树;

        可见前序遍历过程是一个递归过程,在遍历任何一棵子树时,仍然是按照先访问根节点,然后遍历子树根节点的左子树,最后遍历根节点的右子树的顺序进行遍历。

注意这里的访问,指的是得到该节点的 val 值,而不是进入该节点。

递归实现

class Solution:def preorderTraversal(self, root: TreeNode):res = []def preorder(root):if not root:returnres.append(root.val)preorder(root.left)preorder(root.right)preorder(root)return res

前序遍历的显示栈实现:

        根据栈先入后出的特点,先放入右子树,再放入左子树。

        判断二叉树是否为空,为空则直接返回;

        初始化维护一个栈,将根节点入栈;

        当栈不为空时:

                弹出栈顶元素 node,并访问该元素;

                如果 node 的右子树不为空,则将 node 的右子树入栈;

                如果 node 的左子树不为空,则将 node 的左子树入栈;

    def preorderTraversalStack(self, root):if not root:return []res = []stack = [root]while stack:node = stack.pop()res.append(node.val)if node.right:stack.append(node.right)if node.left:stack.append(node.left)return res

二叉树的中序遍历

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

        如果二叉树不为空,则:

                以中序遍历的方式遍历根节点的左子树;

                访问根节点;

                以中序遍历的方式遍历根节点的右子树;

递归实现:

    def inorderTraversal(self, root: TreeNode):res = []def inorder(root):if not root:returninorder(root.left)res.append(root.val)inorder(root.right)inorder(root)return res

中序遍历的显式栈实现:

        访问根节点要在左子树遍历完之后,因此我们应该从根节点开始,循环遍历左子树,不断将当前子树的根节点放入栈中,直到当前节点无左子树时,从栈中弹出该节点并处理;

        判断二叉树是否为空,为空则直接返回;

        初始化维护一个空栈;

        当根节点或者栈不为空时,

                如果当前节点不为空,则遍历左子树,并不断把当前子树的根节点入栈;

                如果当前节点为空,则说明当前节点无左子树,则弹出栈顶元素 node,并访问,然后尝

        试访问该节点的右子树;

    def inorderTraversalStack(self, root):if not root:return []res = []stack = []while root or stack:while root:stack.append(root)root = root.leftnode = stack.pop()res.append(node.val)root = node.rightreturn res

二叉树的后序遍历

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

        如果二叉树不为空,则

                以后序遍历的方式遍历根节点的左子树;

                以后序遍历的方式遍历根节点的右子树;

                访问根节点

递归实现:

    def postorderTraversal(self, root: TreeNode):res = []def postorder(root):if not root:return postorder(root.left)postorder(root.right)res.append(root.val)postorder(root)return res

后序遍历的显式栈实现

        后序遍历中,根节点的访问在左右子树访问之后。

        判断二叉树是否为空,为空则直接返回;

        初始化维护一个空栈,使用 prev 保存前一个访问的节点,用于确定当前节点的右子树是否已经访问完毕;

        当根节点或者栈不为空时,从当前节点开始:

                如果当前节点右左子树,则不断遍历左子树,并将当前根节点压入栈中;

                如果当前节点无左子树,则弹出栈顶元素 node;

                如果栈顶元素 node 无右子树或者右子树已经访问完毕,则访问该元素,然后记录前一

        节点,并将当前节点标记为空节点,意为需要栈弹出一个栈顶元素;

                如果栈顶元素 node 有右子树,则将栈顶元素重新压入栈中,继续访问栈顶元素的右子

        树。

    def postorderTraversalStack(self, root):res = []stack = []prev = Nonewhile root or stack:while root:stack.append(root)root = root.leftnode = stack.pop()if not node.right or node.right == prev:res.append(node.val)prev = noderoot = None     # 需要弹出新的栈顶元素else:stack.append(node)root = node.rightreturn res

二叉树的层序遍历

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

        如果二叉树不为空,则:

                先依次访问二叉树第一层的节点;

                然后依次访问二叉树第二层的节点;

                ...

        层序遍历过程是一个广度优先搜索的过程

二叉树的层序遍历通过队列实现:

        判断二叉树是否为空,为空则直接返回;

        令根节点入队;

        当队列不为空时,求出当前队列长度 s;

        依次从队列中取出这 s 个元素,并对这 s 个元素依次进行访问,然后将其左右孩子节点入队,然后继续遍历下一层节点;

        当队列为空时,结束遍历;

实现代码:

    def levelOrder(self, root: TreeNode):if not root:return []queue = [root]order = []while queue:level = []size = len(queue)for _ in range(size):cur = queue.pop(0)level.append(cur.val)if cur.left:queue.append(cur.left)if cur.right:queue.append(cur.right)if level:order.append(level)return order

104. 二叉树的最大深度

class Solution:def maxDepth(self, root: Optional[TreeNode]) -> int:if not root :return 0return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

103. 二叉树的锯齿形层序遍历

import collections
class Solution:def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:if not root:return []queue = [root]order = []odd = Truewhile queue:level = collections.deque()size = len(queue)for _ in range(size):curr = queue.pop(0)if odd:level.append(curr.val)else:level.appendleft(curr.val)if curr.left:queue.append(curr.left)if curr.right:queue.append(curr.right)if level:order.append(list(level))odd = not oddreturn order

111. 二叉树的最小深度

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def minDepth(self, root: Optional[TreeNode]) -> int:if not root:return 0queue = [root]dep = 1while queue:n = len(queue)for i in range(n):node = queue.pop(0)if node.left == None and node.right == None:return depif node.left:queue.append(node.left)if node.right:queue.append(node.right)dep += 1return dep

        与最大不同,找树的最小深度算是广度遍历的经典应用场景,逐层的排查节点是否是叶子节点,能够找到树的最小深度。

五,二叉树的还原

        指的是通过二叉树的遍历序列,还原出对应的二叉树。

        这个部分比较抽象,所以建议找视频资源进行学习:

        比如:【已知二叉树中序、后序序列,如何还原二叉树?一道题搞定】 https://www.bilibili.com/video/BV1624y1t7Cg/share_source=copy_web&vd_source=87ace2077a752fc4561aff19bae87dac

         简单来说就是根据遍历过程中的规则,不断查找两个遍历结果,实现二叉树的还原,我们的重心在代码实现。而代码实现的重点在于充分明白所知序列能够发挥的作用。

前序序列作用,前序的遍历顺序是【根左右】,因此我们可以从中知道根节点的位置;

中序序列作用,中序遍历的顺序是【左根右】,因此我们知道根节点后,可以知道左子树与右子树的中序序列

后序序列作用,后序遍历的顺序是【左右根】,我们可以知道根节点的位置。

        开始代码实现之前,我们要明确的是,只有前序和后序无法唯一确定二叉树。

从前序和中序序列构造二叉树:

        从前序遍历序列中得到当前根节点位置在 preorder[ 0 ];

        通过在中序遍历中查找上一步根节点对应位置 inorder[ k ],从而将二叉树左右子树分隔开,并得到左右子树节点个数;

        从上一步得到的左右子树个节点个数,将前序序列中的左右子树分开;

        构造当前节点,并递归建立左右树,在左右树的对应位置继续递归遍历并执行上述三步,直到节点为空

    def buildTreeFromPreIn(self, preorder, inorder):def createTree(preorder, inorder, n):if n == 0:return Nonek = 0while preorder[0] != inorder[k]:k += 1  #   找到中序遍历的分隔位置node = TreeNode(inorder[k])node.left = createTree(preorder[1: k + 1], inorder[0: k], k)node.right = createTree(preorder[k + 1: ], inorder[k + 1: ], n - k - 1)return nodereturn createTree(preorder, inorder, len(inorder))

从中序和后序序列构造二叉树

        从后序序列中得到当前根节点的位置在 postorder[ n - 1 ];

        通过在中序遍历中查找根节点对应的位置 inorder[k],从而将二叉树的所有子树分隔开,并得到左右子树的节点个数;

        从上一步得到的左右子树节点个数,将后序序列中的左右子树分开;

        构造当前节点,并递归建立左右子树,并在左右子树对应位置继续递归执行上述三步,直到节点为空

    def buildTreeFromInPost(self, inorder, postorder):def createTree(inorder, postorder, n):if n == 0:return Nonek = 0while postorder[n - 1] != inorder[k]:k += 1node = TreeNode(inorder[k])node.right = createTree(inorder[k + 1: n], postorder[k: n - 1], n - k - 1)node.left = createTree(inorder[0: k], postorder[0: k], k)return nodereturn createTree(inorder, postorder, len(postorder))

从前序和后序序列构造二叉树

        我们可以默认指定前序序列的第 2 个值为左子树的根节点,由此递归划分左右子树。

        从前序遍历序列中可知当前根节点的位置在 preorder[ 0 ];

        从前序序列的第二个值为左子树的根节点,通过在后序序列中查找上一步根节点对应的位置,postorder[ k ]。从而将二叉树的左右子树分隔开,从得到左右子树节点的个数.

        从上一步得到的左右子树个数,将后序序列结果中的左右子树分开;

        构造当前节点,并递归建立左右子树,在左右子树对应位置继续递归遍历并执行上述三步,直到节点为空。

 def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode:def createTree(preorder, postorder, n):if n == 0:return Nonenode = TreeNode(preorder[0])if n == 1:return nodek = 0while postorder[k] != preorder[1]:k += 1node.left = createTree(preorder[1: k + 2], postorder[: k + 1], k + 1)node.right = createTree(preorder[k + 2: ], postorder[k + 1: -1], n - k - 2)return nodereturn createTree(preorder, postorder, len(preorder))

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

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

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

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

相关文章

比较JavaScript中的for...in和for...of循环

🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 🍚 蓝桥云课签约作者、上架课程《Vue.js 和 E…

linux解析域名指令 nslookup 或者 host

host www.baidu.com 第二种方法 nslookup www.baidu.com 注意:ns是name server之意

【包邮送书】Elasticsearch 通过索引阻塞实现数据保护深入解析

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。关…

回收小程序系统后台管理功能

会员管理:管理员可以查看和管理会员的基本信息,如姓名、联系方式、寄送地址和订单记录等。 产品管理:对回收物品进行管理,包括分类、规格设定、数据统计等。 订单管理:对所有订单进行追踪和管理,确保订单处…

Leetcode 543. 二叉树的直径

题目描述: 给你一棵二叉树的根节点,返回该树的 直径 。 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root 。 两节点之间路径的 长度 由它们之间边数表示。 示例 1: 输入:ro…

汽车屏类产品(五):仪表Cluster常用芯片i.MX117x

前言: 仪表一般就是指方向盘前面那个表盘。做仪表的芯片最主要需要支持显示Display,而仪表的主要排版布局多种多样,但是主旨显示内容不尽相同。 仪表需求: 1、rpm转速表盘 仪表Cluster一般会有转速表盘rpm,单位一般是x1000,大部分汽车仪表范围就是0~8,也就是最高8000…

MySQL安装使用(mac、windows)

目录 macOS环境 一、下载MySQL 二、环境变量 三、启动 MySql 四、初始化密码设置 windows环境 一、下载 二、 环境配置 三、安装mysql 1.初始化mysql 2.安装Mysql服务 3.更改密码 四、检验 1.查看默认安装的数据库 2.其他操作 macOS环境 一、下载MySQL 打开 MyS…

用 Qt 不怕被告吗?

用 Qt 不怕被告吗? 在开始前我有一些资料,是我根据网友给的问题精心整理了一份「Qt 的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!! 收律师函不…

云浮肇庆三维扫描钣金铸件抄数逆向设计外观画3D图上门抄数服务

钣金铸件抄数逆向设计画图是一项精密而富有挑战性的工作,它要求工程师具备深厚的专业知识和丰富的实践经验。3D抄数,即是通过精密测量设备获取钣金铸件的三维数据,为后续的逆向设计提供基础。逆向设计则是指根据已有的实物或模型,…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:StepperItem)

用作Stepper组件的页面子组件。 说明: 该组件从API Version 8开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。 子组件 支持单个子组件。 接口 StepperItem() 属性 参数名参数类型参数描述prevLabelstring设置左侧文本按钮内…

2024年软考计划开始了,你准备好了吗?

目录标题 2024年度计算机技术与软件专业技术资格(水平)考试工作计划计算机软考中级科目哪个含金量最高?报考流程和说明 2024年度计算机技术与软件专业技术资格(水平)考试工作计划 一、2024年度计算机软件资格考试(初级…

<Linux> 线程控制

目录 一、线程资源的分配 (一)线程私有资源 (二)线程共享资源 二、原生线程库 三、线程控制接口 (一)线程创建 - pthread_create() 1. 一个线程 2. 一批线程 (二)线程等待 …

linux 新增定时任务

1、创建定时任务 crontab -e 2、加入定时任务规则 0 2 * * * /usr1/local/mysql-backup/backup.sh 说明:backup.sh是sh脚本 3、重启定时任务 service crond restart 扩展 1、查看定时任务列表 crontab -l 2、需要修改定时任务 crontab -e

BUUCTF---web---[护网杯 2018]easy_tornado1

1.点开题目链接 2.依次点开文件链接 3.通过第一个信息可知,flag在文件名为/fllllllllllllag这个里面,第二个信息中的render是渲染函数,第三个个信息中是一个md5加解密,因为filename我们已经知道,所以我们需要找到cooki…

哪些数据可视化工具适合初学者使用?

数据建模及可视化平台,提供一站式全链路数据生命周期管理方案,支持数据建模,支持报表、图谱、大屏可视化应用的快速构建,支持AIBI探索实践,全流程免代码,免费使用 1、功能结构 2、技术架构 3、功能清单

2024年独立站C端只靠SEO还有机会吗?(川圣SEO)蜘蛛池

baidu搜索:如何联系八爪鱼SEO? baidu搜索:如何联系八爪鱼SEO? baidu搜索:如何联系八爪鱼SEO? 2024年独立站C端只靠SEO还有机会吗?#蜘蛛池SEO 都2024年了,你的外贸独立站推广还需要…

盘点Python中4种读取JSON文件和提取JSON文件内容的方法

目录 一、使用json模块的load()方法 二、使用json模块的loads()方法 三、使用pandas库的read_json()方法 四、使用第三方库如requests从网络API获取JSON数据 总结 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,它基于EC…

一文总结CNN中【各类卷积】操作

本文详细总结CNN中各类卷积,旨在指导 domain-specific 更好的模型设计,包括标准卷积,分组卷积(Group Conv),深度可分离卷积(Depthwise Separable Conv),转置卷积&#xf…

官网解决方案之:人力资源网站建设的十大要点

hello,大家好,我是贝格前端工场,从这期开始分享行业网站该如何规划和建设,本期从人力资源网站开始说起。 建设人力资源官网是企业展示自身人力资源管理能力,吸引人才,提升企业形象的重要途径。以下是建设人…

【Linux实践室】Linux 查询命令帮助语句(文末送书)

🌈个人主页:聆风吟_ 🔥系列专栏:Linux实践室、网络奇遇记 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 🔔Linux man命令2.2 🔔Linux info…