详谈二叉树2——二叉树的遍历
1、二叉树的前序遍历(144)
题目描述:
给定一个二叉树,返回它的 前序 遍历。
示例:
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
知识点回顾:
此题考察二树的前序遍历:
前序遍历: 根结点–>左子树–>右子树
题解一: 递归法
思路:
若二叉树为空,则空操作返回;否则:
- 访问根节点;
- 前序遍历根结点的左子树;
- 前序遍历根结点的右子树;
python:
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if not root:
return []
return [root.val]+self.preorderTraversal(root.left)+self.preorderTraversal(root.right)
题解二: 迭代法
以栈的“后进先出”特点实现前序遍历的迭代
从根节点开始,每次迭代弹出当前栈顶元素,并将其孩子结点压入栈中,先压右孩子再压左孩子
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
result=[]
stack=[root]
while stack:#当stack不为空时
node=stack.pop()#出栈
if node:
result.append(node.val)
stack.append(node.right)
#进栈,因栈后进先出,故入栈的顺序与前序遍历顺序相反
stack.append(node.left)
return result
2、二叉树的中序遍历(94)
题目描述:
给定一个二叉树,返回它的中序 遍历。
示例:
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
知识点回顾:
此题考察二树的中序遍历:
中序遍历: 左子树–>根结点–>右子树
题解一: 递归法
思路:
若二叉树为空,则空操作返回;否则:
- 中序遍历根结点的左子树;
- 访问根结点
- 中序遍历根结点的右子树;
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if root is None:
return []
return self.inorderTraversal(root.left)+[root.val]+self.inorderTraversal(root.right)
题解二: 迭代法
进栈出栈法
每到一个节点 A,因为根的访问在中间,将 A 入栈。然后遍历左子树,接着访问 A,最后遍历右子树。
在访问完 A 后,A 就可以出栈了。因为 A 和其左子树都已经访问完成。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def inorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
result=[] #存储遍历结果
stack=[] #栈初始化
cur=root #当前结点指针
#先用指针找到每颗子树的最左下角,然后进行进出栈操作
while stack or cur:
while cur:
stack.append(cur)#进栈
cur=cur.left
cur=stack.pop()#出栈
result.append(cur.val)
cur=cur.right
return result
3、二叉树的后序遍历(145)
题目描述:
给定一个二叉树,返回它的 后序 遍历。
示例:
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
知识点回顾:
此题考察二树的后序遍历:
后序遍历: 左子树–>右子树–>根结点
题解一: 递归法
思路:
若二叉树为空,则空操作返回;否则:
1、后序遍历根结点的左子树;
2、后序遍历根结点的右子树;
3、访问根结点
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
if root is None:
return []
return self.postorderTraversal(root.left)+self.postorderTraversal(root.right)+[root.val]
题解二: 迭代法
思路:
初始化当前节点cur为根结点;栈,存储结果
1、判断当前节点或用户栈是否为空,若不为空,执行下面操作,否则全部流程结束。
2、判断当前节点是否为空,若为空,则执行下一步,若不为空则访问当前节点将当前节点入用户栈,通过while循环执行持续的将右孩子入用户栈的操作。
3、更新当前节点为用户栈出栈节点并更新当前节点为左孩子。
4、返回结果的逆序
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def postorderTraversal(self, root):
"""
:type root: TreeNode
:rtype: List[int]
"""
result=[]
stack=[]
cur=root
while stack or cur:
while cur:
result.append(cur.val)
stack.append(cur)
cur=cur.right
cur=stack.pop()
cur=cur.left
return result[::-1]
4、二叉树的层序遍历(102)
题目描述:
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7],
知识点回顾:
此题考察二树的层序遍历:
二叉树的层序遍历是指从二叉树的第一层(即根结点)开始,从上至下逐层遍历,则按从左到右的顺序对结点逐个访问。
题解: 广度优先遍历法(BFS)
思路:
以队列的方式进行
如果根结点为空,则返回空,否则进行以下步骤:
1.队列Q初始化:根结点入队;
2.循环直至队列Q为空
- 3.1 q=队列Q的队头元素出队;
- 3.2 访问结点q的数据域;
- 3.3若结点q存在左孩子,则将左孩子指针入队;
- 3.4若结点q存在右孩子,则将右孩子指针入队;
注意:本题的输出结果要求是以每层的节点值输出的[[3],[9,20],[15,7]]
,因此需要在上面的基础上添加一个层存储变量:存储每层的结点值,然后再将每层的结果添加到输出结果变量中。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if root is None:
return []
queue=[root]#队列
result=[]#存储结果
while queue:
level=[]#存放一层结果
for i in range(len(queue)):
cur=queue.pop(0)#出队
level.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
if level:
result.append(level)
return result
如果输出结果是以[3,9,20,15,7]
,就可以使用下面的简单模板。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution(object):
def levelOrder(self, root):
"""
:type root: TreeNode
:rtype: List[List[int]]
"""
if root is None:
return []
queue=[root]
result=[]
while queue:
cur=queue.pop(0)
result.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
return result
5、二叉树的层次遍历II(107)
题目描述:
【简单题】
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如:
给定二叉树 [3,9,20,null,null,15,7],
思路分析:
\quad \quad 这道题和「102. 二叉树的层序遍历」相似,不同之处在于,第 102题要求从上到下输出每一层的节点值,而这道题要求从下到上输出每一层的节点值。除了输出顺序不同以外,这两道题的思路是相同的,都可以使用广度优先搜索进行层次遍历。
题解一: 层序遍历+倒转列表
【代码实现】
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
if root is None:
return []
queue=[root]
result=[]
while queue:
level=[]#存放每一层的结果
for i in range(len(queue)):
cur=queue.pop(0)# 出队
level.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
if level:
result.append(level)
return result[::-1]
题解二: 广度优先搜索(BFS)
以队列的方式进行
如果根结点为空,则返回空,否则进行以下步骤:
1.队列Q初始化:根结点入队;
2.循环直至队列Q为空
- 3.1 q=队列Q的队头元素出队;
- 3.2 访问结点q的数据域;
- 3.3若结点q存在左孩子,则将左孩子指针入队;
- 3.4若结点q存在右孩子,则将右孩子指针入队;
3、在遍历完一层节点之后,将存储该层节点值的列表添加到结果列表的头部。 这样,就直接实现了列表的翻转。
【为了添加到头部,可以使用双端队列,在Python中需要调用内置库collections中的deque函数(双端队列),以保存各层结点的值。】
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrderBottom(self, root: TreeNode) -> List[List[int]]:
if root is None:
return []
queue=[root]
result=collections.deque()
while queue:
level=[]#存放每一层的结果
for i in range(len(queue)):
cur=queue.pop(0)# 出队
level.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
if level:
result.appendleft(level)
return list(result)#将双端队列形式转换为列表形式
-
时间复杂度: O ( n ) O(n) O(n),其中 n 是二叉树中的节点个数。每个节点访问一次,结果列表使用链表的结构时,在结果列表头部添加一层节点值的列表的时间复杂度是 O ( 1 ) O(1) O(1),因此总时间复杂度是 O ( n ) O(n) O(n)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n 是二叉树中的节点个数。空间复杂度取决于队列开销,队列中的节点个数不会超过 n。
总结:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
# 递归
# 时间复杂度:O(n),n为节点数,访问每个节点恰好一次。
# 空间复杂度:空间复杂度:O(h),h为树的高度。最坏情况下需要空间O(n),平均情况为O(logn)
# 递归1:二叉树遍历最易理解和实现版本
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
# 前序递归
return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right)
# # 中序递归
# return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right)
# # 后序递归
# return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val]
# 递归2:通用模板,可以适应不同的题目,添加参数、增加返回条件、修改进入递归条件、自定义返回值
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
def dfs(cur):
if not cur:
return
# 前序递归
res.append(cur.val)
dfs(cur.left)
dfs(cur.right)
# # 中序递归
# dfs(cur.left)
# res.append(cur.val)
# dfs(cur.right)
# # 后序递归
# dfs(cur.left)
# dfs(cur.right)
# res.append(cur.val)
res = []
dfs(root)
return res
# 迭代
# 时间复杂度:O(n),n为节点数,访问每个节点恰好一次。
# 空间复杂度:O(h),h为树的高度。取决于树的结构,最坏情况存储整棵树,即O(n)
# 迭代1:前序遍历最常用模板(后序同样可以用)
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
res = []
stack = [root]
# # 前序迭代模板:最常用的二叉树DFS迭代遍历模板
while stack:
cur = stack.pop()
res.append(cur.val)
if cur.right:
stack.append(cur.right)
if cur.left:
stack.append(cur.left)
return res
# # 后序迭代,相同模板:将前序迭代进栈顺序稍作修改,最后得到的结果反转
# while stack:
# cur = stack.pop()
# if cur.left:
# stack.append(cur.left)
# if cur.right:
# stack.append(cur.right)
# res.append(cur.val)
# return res[::-1]
# 迭代1:层序遍历最常用模板
class Solution:
def levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
cur, res = [root], []
while cur:
lay, layval = [], []
for node in cur:
layval.append(node.val)
if node.left: lay.append(node.left)
if node.right: lay.append(node.right)
cur = lay
res.append(layval)
return res
# 迭代2:前、中、后序遍历通用模板(只需一个栈的空间)
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
stack = []
cur = root
# 中序,模板:先用指针找到每颗子树的最左下角,然后进行进出栈操作
while stack or cur:
while cur:
stack.append(cur)
cur = cur.left
cur = stack.pop()
res.append(cur.val)
cur = cur.right
return res
# # 前序,相同模板
# while stack or cur:
# while cur:
# res.append(cur.val)
# stack.append(cur)
# cur = cur.left
# cur = stack.pop()
# cur = cur.right
# return res
# # 后序,相同模板
# while stack or cur:
# while cur:
# res.append(cur.val)
# stack.append(cur)
# cur = cur.right
# cur = stack.pop()
# cur = cur.left
# return res[::-1]
# 迭代3:标记法迭代(需要双倍的空间来存储访问状态):
# 前、中、后、层序通用模板,只需改变进栈顺序或即可实现前后中序遍历,
# 而层序遍历则使用队列先进先出。0表示当前未访问,1表示已访问。
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
stack = [(0, root)]
while stack:
flag, cur = stack.pop()
if not cur: continue
if flag == 0:
# 前序,标记法
stack.append((0, cur.right))
stack.append((0, cur.left))
stack.append((1, cur))
# # 后序,标记法
# stack.append((1, cur))
# stack.append((0, cur.right))
# stack.append((0, cur.left))
# # 中序,标记法
# stack.append((0, cur.right))
# stack.append((1, cur))
# stack.append((0, cur.left))
else:
res.append(cur.val)
return res
# # 层序,标记法
# res = []
# queue = [(0, root)]
# while queue:
# flag, cur = queue.pop(0) # 注意是队列,先进先出
# if not cur: continue
# if flag == 0:
# 层序遍历这三个的顺序无所谓,因为是队列,只弹出队首元素
# queue.append((1, cur))
# queue.append((0, cur.left))
# queue.append((0, cur.right))
# else:
# res.append(cur.val)
# return res
# 莫里斯遍历
# 时间复杂度:O(n),n为节点数,看似超过O(n),有的节点可能要访问两次,实际分析还是O(n),具体参考大佬博客的分析。
# 空间复杂度:O(1),如果在遍历过程中就输出节点值,则只需常数空间就能得到中序遍历结果,空间只需两个指针。
# 如果将结果储存最后输出,则空间复杂度还是O(n)。
# PS:莫里斯遍历实际上是在原有二叉树的结构基础上,构造了线索二叉树,
# 线索二叉树定义为:原本为空的右子节点指向了中序遍历顺序之后的那个节点,把所有原本为空的左子节点都指向了中序遍历之前的那个节点
# emmmm,好像大学教材学过,还考过
# 此处只给出中序遍历,前序遍历只需修改输出顺序即可
# 而后序遍历,由于遍历是从根开始的,而线索二叉树是将为空的左右子节点连接到相应的顺序上,使其能够按照相应准则输出
# 但是后序遍历的根节点却已经没有额外的空间来标记自己下一个应该访问的节点,
# 所以这里需要建立一个临时节点dump,令其左孩子是root。并且还需要一个子过程,就是倒序输出某两个节点之间路径上的各个节点。
# 具体参考大佬博客
# 莫里斯遍历,借助线索二叉树中序遍历(附前序遍历)
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
# cur = pre = TreeNode(None)
cur = root
while cur:
if not cur.left:
res.append(cur.val)
# print(cur.val)
cur = cur.right
else:
pre = cur.left
while pre.right and pre.right != cur:
pre = pre.right
if not pre.right:
# print(cur.val) 这里是前序遍历的代码,前序与中序的唯一差别,只是输出顺序不同
pre.right = cur
cur = cur.left
else:
pre.right = None
res.append(cur.val)
# print(cur.val)
cur = cur.right
return res
# N叉树遍历
# 时间复杂度:时间复杂度:O(M),其中 M 是 N 叉树中的节点个数。每个节点只会入栈和出栈各一次。
# 空间复杂度:O(M)。在最坏的情况下,这棵 N 叉树只有 2 层,所有第 2 层的节点都是根节点的孩子。
# 将根节点推出栈后,需要将这些节点都放入栈,共有 M−1个节点,因此栈的大小为 O(M)。
"""
# Definition for a Node.
class Node:
def __init__(self, val=None, children=None):
self.val = val
self.children = children
"""
# N叉树简洁递归
class Solution:
def preorder(self, root: 'Node') -> List[int]:
if not root: return []
res = [root.val]
for node in root.children:
res.extend(self.preorder(node))
return res
# N叉树通用递归模板
class Solution:
def preorder(self, root: 'Node') -> List[int]:
res = []
def helper(root):
if not root:
return
res.append(root.val)
for child in root.children:
helper(child)
helper(root)
return res
# N叉树迭代方法
class Solution:
def preorder(self, root: 'Node') -> List[int]:
if not root:
return []
s = [root]
# s.append(root)
res = []
while s:
node = s.pop()
res.append(node.val)
# for child in node.children[::-1]:
# s.append(child)
s.extend(node.children[::-1])
return res