递归法
递归思想在二叉树的遍历过程中,思想是非常简单的。前中后分别对应的是树的根节点在什么时候被访问而决定的:
- 前序:最先访问根节点,然后访问左子树,最后访问右子树
- 中序:最先访问左子树,然后访问根节点,最后访问右子树
- 后序:最先访问左子树,然后访问右子树,最后访问根节点
对于一棵二叉树而言,其根节点往下的左孩子和右孩子分别是其左子树和右子树的根节点。所以在上面的描述中使用根节点描述中间节点。
前序
先访问根节点,然后访问左子树,最后访问右子树
def frontPrint(root):
'''
递归前序遍历
'''
if root:
print(root.val, end=' ')
frontPrint(root.left)
frontPrint(root.right)
中序
先访问左子树,然后访问根节点,最后访问右子树
def midPrint(root):
'''
递归中序遍历
'''
if root:
midPrint(root.left)
print(root.val, end=' ')
midPrint(root.right)
后序
先访问左子树,然后访问右子树,最后访问根节点
def behindPrint(root):
'''
递归后序遍历
'''
if root:
behindPrint(root.left)
behindPrint(root.right)
print(root.val, end=' ')
迭代法
对于迭代法而言,我们一般借助数据结构堆栈来实现对二叉树的遍历。这里我们只要弄清左中右三个节点(左子树,根节点,右子树)应该如何进栈就ok了。
这里我们在简单的描述一下堆栈:堆栈是一种数据结构,原则是先进后出,对于这种数据结构比较形象的实物描述是弹夹。对于弹夹而言,当我们往里压子弹的时候,最先压进去的子弹,最后被射出。堆栈就是这样一种数据结构,最先进栈的元素,最后出栈。
有了上面对于堆栈的基本描述,我们就可以比较清晰的分析前中后三种遍历的迭代实现了。
前序
对于前序遍历,我们最先访问的是根节点,然后访问左子树,最后访问右子树。借助堆栈,想要达到这样的效果,我们应该保证右子树最先进栈,其顺序应该为右左中,这样出栈的时候就是中左右。
但是我们还可以换一种思想,我们每次可以先入栈根节点,这样的话根节点一直在栈顶,这时候我们将其出栈访问,根节点就不用参与之后的操作了。这样我们每次只要保证其右子树比左子树先进栈就可以了,由三者顺序入栈变为二者顺序入栈。这样就简单了很多。
def fPrint(root):
'''
迭代前序遍历:
'''
# 空树
if not root: return None
# 初始化堆栈
stack = list()
# 入栈根节点
stack.append(root)
# 堆栈不为空
while stack:
# 出栈对顶元素
p = stack.pop()
# 访问
print(p.val, end=' ')
# 右子树存在,入栈右子树
if p.right:
stack.append(p.right)
# 左子树存在,入栈左子树
if p.left:
stack.append(p.left)
中序
对于中序遍历而言,最左节点最先被访问,所以我们可以从根节点开始将左子树不断入栈,一直到最左节点,然后出栈访问,然后去访问其右子树就可以。
当你找到最左几点,此时其左子树为空,所以其左子树不会入栈,出栈的对顶元素就是最下面这棵二叉树的根节点,访问之后再访问右子树,右子树不为空会继续入栈,为空继续出栈访问,不断的迭代。这里的说明是为了解决部分同学无法理解为什么没有看到根节点的访问过程。
def mPrint(root):
'''
迭代中序遍历
'''
# 空树
if not root: return None
# 初始化堆栈
stack = list()
# 设置访问指针
p = root
while stack or p:
if p:
# 节点不为空一直向左继续入栈
stack.append(p)
p = p.left
else:
# 出栈栈顶元素
p = stack.pop()
# 访问
print(p.val, end=' ')
# 准备访问右子树
p = p.right
后序
后序迭代遍历相对于前序和中序稍微复杂一点,因为根节点是在第二次经过时被访问的。所以需要有一个辅助标记,标记节点是否是第二次被经过。
def bPrint(root):
'''
迭代后序遍历
'''
if not root: return None
# 初始化堆栈
stack = list()
# 初始化标记堆栈
arr = list()
# 操作指针
p = root
while stack or p:
if p:
# 入栈左子树
stack.append(p)
# 使用False标记其是第一次被经过
arr.append(False)
p = p.left
else:
# 出栈对顶元素
p = stack.pop()
# 出栈标记
f = arr.pop()
if p and f:
# 第二次被访问
print(p.val, end=' ')
p = None
else:
# 第一次被访问,重新入栈
stack.append(p)
# 使用True标记第二次被经过
arr.append(True)
# 准备访问其右子树
p = p.right