树类型对比
数据结构 | 定义 | 节点特点 | 遍历方式 | 常见操作 | 时间复杂度(平均) | 时间复杂度(最坏) | 空间复杂度(最坏) | 与其他结构关系 | 应用场景 |
---|---|---|---|---|---|---|---|---|---|
树 | 有根节点,分层级,包含父子、兄弟节点及子树关系的非线性数据结构 | 每个节点可有多条分支,连接多个子节点 | 无特定统一遍历顺序,可按需求自定义遍历逻辑 | 查找、插入、删除(操作复杂度因树结构不同差异大) | 查找、插入、删除: O ( n ) O(n) O(n)(无特殊结构时) | 查找、插入、删除: O ( n ) O(n) O(n) | O ( n ) O(n) O(n)(取决于树中节点数量) | Linked List是特殊化的Tree,Tree是特殊化的Graph | 文件目录结构、组织架构图等场景 |
二叉树 | 每个节点最多有两个子节点的树结构 | 最多两个子节点,分别为左子节点和右子节点 | 前序(根 - 左 - 右)、中序(左 - 根 - 右)、后序(左 - 右 - 根) | 查找、插入、删除(操作复杂度与树的形态有关) | 查找、插入、删除: O ( n ) O(n) O(n)(普通二叉树) | 查找、插入、删除: O ( n ) O(n) O(n)(例如单支树情况) | O ( n ) O(n) O(n)(与树的节点数相关) | 是树的特殊形式 | 在一些简单的决策树模型、算术表达式树中有应用 |
二叉搜索树 | 空树或满足左子树节点值小于根节点值,右子树节点值大于根节点值,且左右子树也为二叉搜索树的二叉树 | 除具备二叉树节点特征外,节点值具有有序性 | 中序遍历结果为升序排列,还有前序、后序遍历 | 查询、插入、删除 | 查询、插入、删除: O ( log n ) O(\log n) O(logn) | 查询: O ( n ) O(n) O(n)(树严重不平衡时) 插入、删除: O ( n ) O(n) O(n)(树严重不平衡时) | O ( n ) O(n) O(n)(取决于节点数量) | 是二叉树的特殊形式 | 用于数据搜索、排序场景,如数据库索引、符号表管理 |
前中后序遍历
前序遍历、中序遍历和后序遍历是二叉树的三种常见遍历方式,它们的定义和特点如下:
- 前序遍历(Pre-order Traversal):按照“根节点 - 左子树 - 右子树”的顺序访问二叉树的节点。即先访问根节点,然后递归地对左子树进行前序遍历,最后递归地对右子树进行前序遍历。这种遍历方式可以用于创建二叉树的副本,或者在处理树的根节点相关操作先于子树操作的场景中使用。 例如,对于二叉树
A
(根节点),左子树节点B
,右子树节点C
,前序遍历的顺序就是A
、B
、C
。 - 中序遍历(In-order Traversal):访问顺序为“左子树 - 根节点 - 右子树”。先递归地遍历左子树,然后访问根节点,最后递归地遍历右子树。对于二叉搜索树,中序遍历会按升序访问所有节点,因此常用于对二叉搜索树进行排序或有序输出节点值。例如,若二叉树
A
(根节点),左子树节点B
,右子树节点C
,中序遍历的顺序就是B
、A
、C
。 - 后序遍历(Post-order Traversal):按照“左子树 - 右子树 - 根节点”的顺序进行遍历。先递归地遍历左子树,再递归地遍历右子树,最后访问根节点。后序遍历适用于在处理完子树的所有节点后,再对根节点进行操作的场景,比如计算树中所有节点的值之和,需要先计算左右子树节点值之和,最后加上根节点的值。例如,对于上述二叉树,后序遍历的顺序就是
B
、C
、A
。
这三种遍历方式都是基于递归的思想实现的,在实际应用中,可以根据具体需求选择合适的遍历方式来处理二叉树相关的问题。同时,也可以通过栈或其他数据结构将递归遍历转换为非递归的形式进行实现。
def preorder(self, root):if root:self.traverse_path.append(root.val)self.preorder(root.left)self.preorder(root.right)def inorder(self, root):if root:self.inorder(root.left)self.traverse_path.append(root.val)self.inorder(root.right)def postorder(self, root):if root:self.postorder(root.left)self.postorder(root.right)self.traverse_path.append(root.val)
二叉搜索树
二叉搜索树(Binary Search Tree,BST) ,也叫二叉查找树、二叉排序树 ,它要么是空树,要么是满足以下性质的二叉树:
节点值有序性
- 若左子树不为空,左子树上所有节点的值均小于根节点的值 。
- 若右子树不为空,右子树上所有节点的值均大于根节点的值 。
- 且其左、右子树也分别为二叉搜索树。
中序遍历有序性
对二叉搜索树进行中序遍历(左子树 - 根节点 - 右子树 ),会得到一个递增的有序序列 。
这种结构在数据操作上具有优势:
查找操作
与二分查找思想类似,通过和当前节点值比较,若目标值小于当前节点值,在左子树查找;若大于,则在右子树查找 。平均时间复杂度为 O ( log n ) O(\log n) O(logn)( n n n为节点数) ,但树不平衡退化为链表时,时间复杂度会变为 O ( n ) O(n) O(n) 。
插入操作
新节点总是作为叶子节点插入。从根节点开始比较,若新节点值小于当前节点值,往当前节点左子树插入;若大于,则往右子树插入 ,直到找到合适位置。平均时间复杂度 O ( log n ) O(\log n) O(logn) 。
删除操作
- 叶子节点:直接删除 。
- 只有左子树或右子树的节点:让其子树直接成为其双亲节点的子树 。
- 左右子树都存在的节点:通常用其右子树最小节点(或左子树最大节点 )替代,再删除该替代节点 。平均时间复杂度 O ( log n ) O(\log n) O(logn) 。
二叉搜索树应用广泛,像数据库系统、文件系统常利用它实现高效的数据检索和管理 。 但它存在不平衡问题,为解决该问题,有AVL树、红黑树等自平衡二叉搜索树变种 。
树的面试题
题目复述
给定二叉树的根节点 root
,按中序遍历(左子树 - 根节点 - 右子树)顺序,返回树中所有节点的值。
最优解
# 定义二叉树节点类
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def inorderTraversal(self, root):result = []def inorder(root):if root:inorder(root.left)result.append(root.val)inorder(root.right)inorder(root)return result
分析
- 递归思路:定义内部函数
inorder
实现递归。先判断根节点root
是否存在,若存在,先递归调用inorder(root.left)
处理左子树,此时会层层深入左子树,直到叶子节点。然后将当前根节点的值root.val
添加到结果列表result
,最后递归调用inorder(root.right)
处理右子树 。 - 复杂度:时间复杂度为 O ( n ) O(n) O(n) , n n n 为二叉树节点数,因为每个节点都会被访问一次。空间复杂度在最好情况下为 O ( log n ) O(\log n) O(logn) (树近似完全平衡时,递归调用栈深度为树高) ,最坏情况下为 O ( n ) O(n) O(n) (树退化为单链表形式,递归调用栈深度等于节点数) 。
- 优势:相比非递归解法,代码更简洁直观,逻辑清晰,易于理解和编写,在代码简洁性和易维护性上表现出色,所以是本题较优选择。
题目2:二叉树的前序遍历
题目复述
给定一个二叉树的根节点 root
,按照前序遍历(根节点 - 左子树 - 右子树)的顺序,返回树中所有节点的值。
最优解
Python递归解法
# 定义二叉树节点类
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def preorderTraversal(self, root):result = []def preorder(root):if root:result.append(root.val)preorder(root.left)preorder(root.right)preorder(root)return result
分析
- 递归思路:在内部函数
preorder
中,先判断root
是否存在。若存在,首先将根节点的值root.val
加入结果列表result
,这体现前序遍历先访问根节点的特点。然后递归调用preorder(root.left)
访问左子树,再递归调用preorder(root.right)
访问右子树 。 - 复杂度:时间复杂度为 O ( n ) O(n) O(n) ,其中 n n n 是二叉树的节点个数,因为要对每个节点进行一次访问操作。空间复杂度方面,最好情况(树完全平衡 )下为 O ( log n ) O(\log n) O(logn) ,此时递归调用栈深度为树高;最坏情况(树退化为链表 )下为 O ( n ) O(n) O(n) ,递归调用栈深度等于节点数 。
- 优势:递归解法代码简洁明了,清晰地遵循前序遍历的逻辑,相比非递归实现(需借助栈模拟递归过程,代码更复杂 ),在理解和编写上更具优势,所以是本题较优的实现方式。
题目3:N 叉树的后序遍历
题目复述
给定一个 N 叉树的根节点 root
,按照后序遍历(左子树 - 右子树 - 根节点 ,这里 N 叉树的多个子树也按类似顺序,先遍历完所有子树,最后访问根节点)的顺序,返回树中所有节点的值。
最优解
Python递归解法
# 定义N叉树节点类
class Node:def __init__(self, val=None, children=None):self.val = valself.children = children if children is not None else []class Solution:def postorder(self, root):result = []def post(root):if root:for child in root.children:post(child)result.append(root.val)post(root)return result
分析
- 递归思路:定义内部函数
post
实现递归。先判断root
是否存在,若存在,遍历根节点的所有子节点root.children
,对每个子节点递归调用post(child)
,这相当于先处理完所有子树。处理完子树后,将根节点的值root.val
添加到结果列表result
,符合后序遍历的逻辑。 - 复杂度:时间复杂度为 O ( n ) O(n) O(n) , n n n 为 N 叉树的节点个数,因为每个节点都会被访问一次。空间复杂度方面,最好情况(树比较平衡 )下为 O ( log n ) O(\log n) O(logn) ,此时递归调用栈深度与树高相关;最坏情况(树退化为链表状 )下为 O ( n ) O(n) O(n) ,递归调用栈深度等于节点数。
- 优势:递归解法代码简洁,很好地契合 N 叉树后序遍历的逻辑,相较于使用栈等数据结构的非递归实现(非递归实现要模拟递归调用栈,逻辑更复杂 ),在代码的可读性和编写难度上更具优势,所以是本题较优的选择。
题目4:N 叉树的前序遍历
题目复述
给定一个 N 叉树的根节点 root
,按照前序遍历(根节点 - 子树 ,依次访问完根节点后,再按顺序访问各个子树)的顺序,返回树中所有节点的值。
最优解
Python递归解法
# 定义N叉树节点类
class Node:def __init__(self, val=None, children=None):self.val = valself.children = children if children is not None else []class Solution:def preorder(self, root):result = []def pre(root):if root:result.append(root.val)for child in root.children:pre(child)pre(root)return result
分析
- 递归思路:在内部函数
pre
中,先判断root
是否存在。若存在,首先将根节点的值root.val
加入结果列表result
,这体现前序遍历先访问根节点的特点。然后遍历根节点的所有子节点root.children
,对每个子节点递归调用pre(child)
,依次访问各个子树。 - 复杂度:时间复杂度为 O ( n ) O(n) O(n) ,其中 n n n 是 N 叉树的节点个数,因为要对每个节点进行一次访问操作。空间复杂度方面,最好情况(树比较平衡 )下为 O ( log n ) O(\log n) O(logn) ,此时递归调用栈深度与树高相关;最坏情况(树退化为链表状 )下为 O ( n ) O(n) O(n) ,递归调用栈深度等于节点数。
- 优势:递归解法代码简洁直观,清晰地遵循 N 叉树前序遍历的逻辑。相比非递归实现(需借助栈等数据结构来模拟递归过程,代码相对复杂 ),在理解和编写上更简单,所以是本题较优的实现方式。
题目5:验证二叉搜索树
题目复述
给定一个二叉树的根节点 root
,判断其是否为有效的二叉搜索树 。有效二叉搜索树需满足:左子树的所有节点的值均小于根节点的值;右子树的所有节点的值均大于根节点的值;且左、右子树也分别为二叉搜索树。
最优解
# 定义二叉树节点类
class TreeNode:def __init__(self, val=0, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def isValidBST(self, root):def check(root, min_val, max_val):if not root:return Trueif root.val <= min_val or root.val >= max_val:return Falsereturn check(root.left, min_val, root.val) and check(root.right, root.val, max_val)return check(root, float('-inf'), float('inf'))
分析
- 递归思路:定义内部函数
check
进行递归验证。check
函数接收当前节点root
,以及当前节点值的取值范围min_val
和max_val
。每次递归时,先检查当前节点root
的值是否在合理范围内(大于min_val
且小于max_val
),若不满足则返回False
。然后对左子树递归调用check
,并更新取值范围为(min_val, root.val)
,对右子树递归调用check
,更新取值范围为(root.val, max_val)
。如果所有节点都满足条件,则返回True
。 - 复杂度:时间复杂度为 O ( n ) O(n) O(n) ,其中
n
是二叉树的节点数,因为需要遍历每个节点进行判断。空间复杂度在最好情况下为 O ( log n ) O(\log n) O(logn) (树高度平衡时,递归调用栈深度与树高相关 ),最坏情况下为 O ( n ) O(n) O(n) (树退化为链表时,递归调用栈深度等于节点数) 。 - 优势:这种递归验证方式代码简洁且逻辑清晰,直接基于二叉搜索树的定义进行判断,相比其他复杂的方法(如先中序遍历再判断结果是否有序,虽然可行但多了一次额外的遍历操作 ),效率更高且代码更紧凑,所以是本题较优的解法。