码随想录算法训练营Day13 | 二叉树的各种遍历
二叉树理论知识
文章讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
种类:
满二叉树
完全二叉树
二叉搜索树
平衡二叉搜索树
存储方式:
链式存储
线性存储
遍历方式
深度优先搜索(可用递归法、迭代法):前序遍历、中序遍历、后序遍历
广度优先搜索(迭代法):层序遍历
定义方式
class TreeNode:
def __init__(self, val, left = None, right = None):
self.val = val
self.left = left
self.right = right
递归遍历
题目链接/文章讲解/视频讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86.html
前序
from typing import List, Optional
# Definition for a binary tree node.
class TreeNode:
def __init__(self, val, left = None, right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def recursion(self, cur: Optional[TreeNode], l = List[int]) -> None:
# 确定终止条件
if cur == None:
return None
# 确定单层递归的逻辑
# 前序遍历:中->左->右
# 中
l.append(cur.val)
# 左
self.recursion(cur.left, l)
# 右
self.recursion(cur.right, l)
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
self.recursion(root, result)
return result
时间复杂度:O(n),其中 n 是二叉树的节点数。每一个节点恰好被遍历一次。
空间复杂度:O(n),为递归过程中栈的开销,平均情况下为 O(logn),最坏情况下树呈现链状,为 O(n)。
后序
class Solution:
def recursion(self, cur : Optional[TreeNode], l : List[int]) -> None:
# 确定终止条件
if cur == None:
return None
# 确定单层递归逻辑
# 左
self.recursion(cur.left, l)
# 右
self.recursion(cur.right, l)
# 中
l.append(cur.val)
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
self.recursion(root, result)
return result
中序
class Solution:
def recursion(self, cur = Optional[TreeNode], l = List[int]) -> None:
if cur == None:
return None
# 左
self.recursion(cur.left, l)
# 中
l.append(cur.val)
# 右
self.recursion(cur.right, l)
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
result = []
self.recursion(root, result)
return result
迭代遍历
题目链接/文章讲解/视频讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E8%BF%AD%E4%BB%A3%E9%81%8D%E5%8E%86.html
前序
class TreeNode:
def __init__(self, val, left = None, right = None):
self.val = val
self.left = left
self.right = right
class Solution:
def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
'''迭代法'''
# 栈用来遍历节点元素
stack = []
# 列表用来存储结果
result = []
stack.append(root)
while stack:
node = stack[-1]
stack.pop()
# 若node不为空则加入到result中
if node:
result.append(node.val)
# 先压入右子节点,出栈时右子节点后出,符合前序遍历的中左右中的右排最后
stack.append(node.right)
# 后压入左子节点,出栈时左子节点先出
stack.append(node.left)
else:
continue
后序
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
'''
整体思路为按照【中右左】的顺序用迭代法将树节点依次放入列表中,最后反转列表实现【左右中】
'''
stack = []
result = []
stack.append(root)
while stack:
node = stack[-1]
stack.pop() # 记得要从栈中把元素弹出!
if node:
result.append(node.val)
# 进栈时先进左子节点,则出栈时左子节点后出
stack.append(node.left)
# 进站时先进右子节点,则出栈时右子节点后出
stack.append(node.right)
else:
continue
# 记得还要反转列表
# result = list(reversed(result)) #reversed() 函数用于反转一个可迭代对象(如列表、元组、字符串等)。它返回一个反转的迭代器,而不是直接返回一个列表,因此它的返回值是一个迭代器,你可以通过将其转换为列表、元组或其他数据结构来使用
result = result[::-1]
return result
中序 !!!!
class Solution:
def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
'''
访问当前节点若不为空,则放入栈,指针再指向左孩子;若为空,则从栈中弹出元素,放入结果集,指针再指向右孩子
'''
# 用栈存放遍历过的元素
stack = []
# 用列表记录元素
result = []
cur = root
while cur or stack:
# 访问当前节点若不为空,则放入栈,代表已访问过当前节点,但还不用记录
# 节点更新为左子节点
if cur:
stack.append(cur)
cur = cur.left
# 若为空,则从栈中弹出元素,记录该节点
# 节点更新为右子节点
else:
cur = stack.pop()
result.append(cur.val)
cur = cur.right
return result
统一迭代
题目链接/文章讲解:https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E7%BB%9F%E4%B8%80%E8%BF%AD%E4%BB%A3%E6%B3%95.html
空指针法
以后序为例
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
'''
统一迭代法中,访问的节点和要处理的节点不一致,
所以当访问过的节点入栈时,在该类节点后再放入空指针Null,表示已访问但未处理
'''
stack = []
result = []
# 加上if root防止碰到空输入
if root:
stack.append(root)
while stack:
node = stack.pop()
# 若该node不为空,则说明该node未被访问过,按照中右左的顺序依次将节点放入站,并在该node后放入null
if node:
# 中
stack.append(node)
stack.append(None)
# 右:注意注意!!!一定要判断是否确定有左右孩子,有才放进栈中,否则若直接不加判断就放入(空)孩子,会和用来标记的空指针混淆
if node.right:
stack.append(node.right)
# 左
if node.left:
stack.append(node.left)
# 若node为空,表示该节点之前已被访问过,此时可以处理该节点了
else:
node = stack.pop()
result.append(node.val)
return result
boolean值法
以后序为例
class Solution:
def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
'''
boolean统一迭代法中,访问的节点和要处理的节点不一致,
所以给每个节点进栈时加一个Boolean值标记。
boolean为True代表之前已经访问过该节点,可以进行收割;False表示首次访问,此次访问目的为将该节点及其左右孩子放入栈中
'''
stack = [(root, False)] if root else []
result = []
while stack:
node, boolean = stack.pop()
# boolean为true表示已访问该节点,可以进行收割
if boolean:
result.append(node.val)
# boolean为false表示首次访问。此次访问的目的是“把自己和两个儿子在栈中安排好位次”
# 但一定要判断左右孩子是否存在,存在才有必要放入栈中
# 因为是后序遍历,则进栈顺序为中右左
else:
# 中
stack.append((node, True))
# 右
if node.right:
stack.append((node.right, False))
# 左
if node.left:
stack.append((node.left, False))
return result
层序遍历
题目链接/文章讲解/视频讲解:https://programmercarl.com/0102.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E5%BA%8F%E9%81%8D%E5%8E%86.html
from collections import deque
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
'''
将每层的节点依次放入队列中,并记录每层结点的个数
'''
result = []
# 队列用来按层依次放入节点
# 注意这里要用deque[root]而不是deque(root)
que = deque([root]) if root else deque()
# 只要que有元素,就代表这一层有节点
while que:
# 先确定该层有多少个节点
size = len(que)
LevelResult = []
# 每一次循环中将出队元素的左右孩子节点(若有)依次放入队列中
# 出队元素记录在每一层的临时结果中
for _ in range(size):
node = que.popleft()
LevelResult.append(node.val)
# 一定要判断孩子是否存在啊!!!!!
if node.left:
que.append(node.left)
if node.right:
que.append(node.right)
result.append(LevelResult)
return result
时间复杂度:O(n),因为每个节点只遍历了一次