树是一种由节点(node)和边(edges)构成层级关系的结构。
如果你了解 linux 文件结构(tree 命令),它的结构也是一棵树。
我们快速看下树涉及到的一些概念:
二叉树是树结构里面最常用的一种树结构,其实二叉树就是一种简单的树。
二叉树的每个节点最多包含两个孩子。
从上边的图来看几个二叉树的概念:
一棵 size 为 n 的二叉树高度最多可以是 n,最小的高度是 ⌊lgn⌋+1,这里 log 以 2 为底简写为 lgn。
一棵二叉树的前序遍历结果 = 根节点 + 左子树的前序遍历结果 + 右子树的前序遍历结果。
可以理解为有序的遍历二叉树。
class Solution:
def preorderTraversal(self, root:TreeNode) -> List[int]:
self.res = []
self.traverse(root) #从 root 开始遍历
return self.res # 返回的是 list
def traverse(self, root: TreeNode):
if root == None:
return
self.res.append(root.val) # 前序遍历位置
self.traverse(root.left)
self.traverse(root.right)
二叉树的中序遍历,一般在二叉搜索树(BST)中最为常用。
你完全可以把 BST 的中序遍历认为是遍历有序数组。
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
self.res = []
self.traverse(root)
return self.res
def traverse(self, root: TreeNode):
if root == None:
return
self.traverse(root.left)
self.res.append(root.val) #中序遍历位置
self.traverse(root.right)
后序遍历通过递归的返回,返回了二叉树子节点的信息。
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
self.res = []
self.traverse(root)
return self.res
def traverse(self, root: TreeNode):
if root == None:
return
self.traverse(root.left)
self.traverse(root.right)
self.res.append(root.val) #后续遍历位置
对于二叉树的遍历,前序遍历执行是自顶向下的,而后序遍历执行是自底向上的。
以上三种遍历过程是通过递归来实现的,也是 DFS 在二叉树中的应用 。
在应用中,二叉树的递归方法使用可以分两类思路:
遇到问题是否可以通过遍历一遍二叉树得到答案**(前序遍历)**?
如果不能的话,是否可以通过分解问题,用子问题(子树)的答案推导出原问题的答案**(后序遍历)**。
用几个例子来看一下:
求二叉树的最大深度,也就是求这棵树的左右子树深度最大的值,直接遍历二叉树就能解决此问题。
# 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 maxDepth(self, root: Optional[TreeNode]) -> int:
return self.traverse(root)
def traverse(self, root):
if not root:
return 0
return max(self.traverse(root.left), self.traverse(root.right)) + 1 # 递归,左右子树的最大深度
# 加 1 是加上根节点的深度。
根据题目,直径可以理解为任意节点的左右子树的最大深度之和。
那么直接的想法是对每个节点计算左右子树的最大高度,得出每个节点的直径,从而得出最大的那个直径。
解决这题的关键在于,每一条二叉树的直径长度,就是一个节点的左右子树的最大深度之和。
这就是把原问题分解为子问题的方法。
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
self.max = 0
self.traverse(root)
return self.max
def traverse(self, root):
if not root:
return 0
l = self.traverse(root.left) #求该节点左子树的深度
r = self.traverse(root.right)#求该节点右子树的深度
self.max = max(self.max, l+r) # 后续遍历更新最大的直径
return max(l,r) + 1 #这也是后序遍历的位置。
# 由于利用后序遍历从低向上计算子问题,其含义是返回当前节点(子节点)中左右子树深度最大的那一个,用于后续计算最大路径。