目录
二叉树的递归遍历
题目链接:
文章讲解:代码随想录
解题卡点:第一次见二叉树,学习思路
二叉树主要有两种遍历方式:
深度优先遍历:先往深走,遇到叶子节点再往回走。①前序遍历 (递归法,迭代法);②中序遍历 (递归法,迭代法);③后序遍历 (递归法,迭代法)。
广度优先遍历:一层一层的去遍历。层次遍历 (迭代法)。
这里的前中后,指的是中间节点的遍历顺序。
递归的三要素写法:
①确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么,进而确定递归函数的返回类型。
②确定终止条件:写完了递归算法,运行的时候经常会遇到栈溢出的错误,这是没写终止条件或者终止条件写的不对。操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
③确定单层递归的逻辑:确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
以前序遍历为例:
①确定递归函数的参数和返回值:函数传入node获取当前节点信息,函数不需要有返回值,只需return跳出当前递归就行。
②确定终止条件:在递归的过程中,若当前遍历的节点为None,那么本层递归就要结束。
③确定单层递归的逻辑:前序遍历是中左右的顺序,所以在单层递归的逻辑,要先取中节点的数值,随后递归的进行左节点和右节点。
# 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 preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def dfs_preorder(node):
if node is None:
return
res.append(node.val)
dfs_preorder(node.left)
dfs_preorder(node.right)
dfs_preorder(root)
return res
# 深度优先_递归中序遍历
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def dfs_inorder(node):
if node is None:
return
dfs_inorder(node.left)
res.append(node.val)
dfs_inorder(node.right)
dfs_inorder(root)
return res
# 深度优先_递归后序遍历
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
def dfs_postorder(node):
if node is None:
return
dfs_postorder(node.left)
dfs_postorder(node.right)
res.append(node.val)
dfs_postorder(root)
return res
二叉树的迭代遍历
题目链接:
文章讲解:代码随想录
解题卡点:第一次见二叉树,学习思路
前序遍历:中左右,每次先处理中间节点。先将根节点放入栈中,然后将右子节点加入栈,再加入左子节点,确保出栈的顺序是中左右。
后序遍历:调整前序遍历的子节点顺序,再反转结果列表即可。
中序遍历:前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,要访问的元素和要处理的元素顺序是一致的。而中序遍历左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点,造成了处理顺序和访问顺序不一致的问题。
因此中序遍历需要用指针的遍历来帮助访问节点,用栈来处理节点上的元素。
本题思路:
①指针cur=root,然后不断访问cur.left,找到整个二叉树最左的节点。在这个过程中,路径上的所有节点都要入栈。这其实是为了记录一层层的父节点。
②如果cur为空,说明当前这一层级的二叉树已经处理完了,就从栈中弹出一个待处理的节点,令cur指向它,再把它的数值加入结果集。
③接下来再次移动指针,使cur=cur.right,开始处理cur的右节点。
④此时就开始了下一次大循环。如果cur不为空,就一路去找最左的节点,和之前一样的逻辑。
# 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 preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
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: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = []
cur = root
while cur or stack:
if cur:
stack.append(cur)
cur = cur.left
else:
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
# 深度优先_迭代后序遍历
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
if not root:
return []
res = []
stack = [root]
while stack:
node = stack.pop()
res.append(node.val)
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
res.reverse()
return res
二叉树的统一迭代
题目链接:
文章讲解:代码随想录
解题卡点:第一次见二叉树,学习思路
前中后序的统一风格迭代法代码。
由于无法同时解决访问节点 (遍历节点) 和处理节点 (将元素放进结果集) 不一致的情况,那么就将访问的节点放入栈中,把要处理的节点也放入栈中并做好标记。
①空指针标记法:要处理的节点放入栈之后,紧接着放入一个空指针作为标记。
总结一下上面的实现过程:
①在栈里放入root节点,当栈不为空时,弹出栈顶节点。接下来有两种情况:
②如果弹出的node不为None,按照遍历顺序的逆序,依次向栈里加入左中右三个节点,加入中节点时后面加一个None。
③如果弹出的node为None,就继续弹出一个节点,将该节点的val加入结果集。
可以看到只在②中入栈,在③中进行记录,以此实现了迭代代码的统一。
上面的判断中,很重要的一个点是判断栈顶元素是否为空,原因在与压栈的时候对节点进行了标记,说明该节点是中间节点,如果栈顶元素为空,说明用NULL标记的这个节点的左节点已经处理了或没有左子节点,现在可以处理中间节点了,而如果栈顶元素不为空,说明该节点还不是还有左子节点,因此需要将其子节点继续压栈。假设遍历到某叶子节点,因为它没有子节点,所以按照一定的顺序压栈后,栈顶元素就是NULL,这是关键!!!
# 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 preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
stack = []
if root:
stack.append(root)
while stack:
node = stack.pop()
if node != None:
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
stack.append(node)
stack.append(None)
else:
node = stack.pop()
res.append(node.val)
return res
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
stack = []
if root:
stack.append(root)
while stack:
node = stack.pop()
if node != None:
if node.right:
stack.append(node.right)
stack.append(node)
stack.append(None)
if node.left:
stack.append(node.left)
else:
node = stack.pop()
res.append(node.val)
return res
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
stack = []
if root:
stack.append(root)
while stack:
node = stack.pop()
if node != None:
stack.append(node)
stack.append(None)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
else:
node = stack.pop()
res.append(node.val)
return res
②布尔标记法:加一个布尔值跟随每个节点,False (默认值) 表示需要为该节点和它的左右子节点安排在栈中的位次,True表示该节点的位次之前已经安排过了,可以放入结果数组中。
# 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 inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
res = []
stack = [(root, False)] if root else []
while stack:
node, visited = stack.pop()
if visited:
res.append(node.val)
continue
if node.right:
stack.append((node.right, False))
stack.append((node, True))
if node.left:
stack.append((node.left, False))
return res
二叉树的层序遍历
题目链接:
107. 二叉树的层序遍历 II - 力扣(LeetCode)
515. 在每个树行中找最大值 - 力扣(LeetCode)
116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)
117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode)
文章讲解:代码随想录
解题卡点:第一次见二叉树,学习思路
队列→先进先出→符合一层一层遍历的逻辑
栈→先进后出→适合递归的逻辑
层序遍历就是从上到下,从左到右地输出每一层。使用队列来完成层序遍历,初始化时放入root节点。每出队一个节点,将其val加入结果集,然后再将它的左右节点入队。
# 102. 二叉树的层序遍历
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
res = []
queue = collections.deque([root])
while queue:
level = []
level_size = len(queue)
for _ in range(level_size):
cur = queue.popleft()
level.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
res.append(level)
return res
102是层序遍历基础,后面的题都是对102的应用。其中需要注意429N叉树的寻找子节点,116的节点next链接,111树最小深度的条件。