二叉树遍历算法(递归与非递归)
学习目标:
本文带大家梳理一下非递归的二叉树遍历算法设计流程,方便大家有一个直观的了解。
一、引言
二叉树的遍历想必大家都已经很熟悉了,二叉树主要分前序遍历、中序遍历、后续遍历和层次遍历四种遍历方法,相关的算法设计我们也能熟练掌握。在算法设计中,我们一般采用递归的思想来完成二叉树的四种遍历。本文带大家一起总结一下二叉树的非递归遍历算法流程,包含二叉树的前序、中序和后序遍历算法。
二、递归算法回顾
递归是算法设计中的一个重要思想,其包含两个基本部分:递归调用和结束条件。先附上递归形式的二叉树前序、中序和后序遍历算法代码(Python3):
class solution():
def __init__(self, bitree):
self.pst = ''
self.ist = ''
self.post = ''
self.len = len(bitree)
self.tree = bitree
self.order(0)
def order(self, n):
if n >= self.len:
return 0
if self.tree[n] != -1 : self.pst += str(self.tree[n]) + ' '
self.order((n+1)*2-1)
if self.tree[n] != -1 : self.ist += str(self.tree[n]) + ' '
self.order((n+1)*2)
if self.tree[n] != -1 : self.post += str(self.tree[n]) + ' '
return 0
if __name__ == '__main__':
solu = solution([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
print(solu.pst)
print(solu.ist)
print(solu.post)
上述代码分别输出给定二叉树的前、中、后序三种遍历方式的输出序列,solution的输入格式为列表(树的完全二叉树形式的层次遍历序列,没有节点的位置暂时用‘-1’代替)。对于如下二叉树,我们的输入格式为:[6, 3, 7, 2, 5, -1, 8]
运行代码后,会输出正确的三种遍历结果:
6 3 2 5 7 8
2 3 5 6 7 8
2 5 3 8 7 6
递归下的二叉树遍历较为简单,我们不再介绍。
三、调用栈结构的算法
本节我们来介绍非递归的二叉树遍历,自然想到的是利用栈来实现。附上处理流程图,能够一目了然。
二叉树遍历非递归算法流程图(前序):
二叉树遍历非递归算法流程图(中序):
二叉树遍历非递归算法流程图(后序):
通过上述总结的流程图,我们可以清晰地看出栈结构的各种遍历方式下数据处理以及指针转移的过程。其中前序遍历和中序遍历序列的处理流程有着很大的相似之处,只是访问的时间节点不同,前者在新结点入栈后立即访问;后者入栈后还需要沿着左分支深度搜索,直到遇到空指针后再顺序出栈访问。
相比于前序遍历和中序遍历,后续遍历的非递归实现明显要复杂很多。后续遍历序列之所以复杂,是由于在其过程中,某一个结点如果有左右孩子,其需要等到左右孩子都被访问结束后才能访问,对应到处理流程中也就是右孩子结点入栈前其父结点依然不能从栈中释放,这在分支判断中就稍微复杂了些。因为我们需要在某一次栈pop操作后判断出栈的这个结点的右孩子有没有被访问过,否则算法可能会一直访问右孩子,造成死循环。
因此我们需要为每一个结点添加一个“是否搜索过右孩子”的标志,初始化时所有结点标志置“未搜索”,在其右孩子被算法处理并添加到栈中后标志置“已搜索”。这个标志的添加方式也有很多种,如果我们把树的结点定义为一个结构体或类,可以在其中添加成员变量;如果树的定义仅仅是个列表(像第二章中给出的输入示例),我们可以定义一个同样大小的列表,定义”已搜索“和”未搜索“的标志并初始化(标志可以为布尔值,或者’0、1‘)。
三种遍历方式总结:
(1)前序遍历:
- 判断根结点是否为空,不为空时访问并入栈;
- 指向栈顶结点,沿该结点向左孩子方向一直搜索,将途中任意结点入栈并访问;
- 没有左孩子时,出栈并判断出栈结点是否有右孩子(出栈时不必判断栈是否为空,因为之前至少有一次入栈操作了),有则将右孩子入栈,执行第2步。
- 没有右孩子时,需要判断栈是否为空,非空时继续出栈并判断有无右孩子,有则右孩子入栈,执行第2步;没有则执行本步。以上操作以栈为空为终止条件。
(2)中序遍历:
- 判断根结点是否为空,不为空时入栈;
- 指向栈顶结点,沿该结点向左孩子方向一直搜索,将途中任意结点入栈;
- 没有左孩子时,出栈并访问出栈结点(出栈时不必判断栈是否为空,因为之前至少有一次入栈操作了),然后判断出栈结点是否有右孩子,有则将其入栈,重复第2步。
- 没有右孩子时,需要判断栈是否为空,非空时继续出栈并判断有无右孩子,有则右孩子入栈,执行第2步;没有则执行本步。以上操作以栈为空为终止条件。
(3)后序遍历:
- 判断根结点是否为空,不为空时入栈,初始化所有结点的“右孩子搜索”标记为“未搜索”状态;
- 指向栈顶结点,沿该结点向左孩子方向一直搜索,将途中任意结点入栈;
- 没有左孩子时,指向栈顶结点并判断该结点是否拥有右孩子,如有则置该结点“右孩子搜索”标志为“搜索过“,并将右孩子入栈,重复第2步。没有右孩子时,执行第4步。
- 访问当前结点并出栈,判断栈是否为空,如果是则结束算法,如果不是则继续指向栈顶结点并执行第5步。
- 判断栈顶结点“右孩子搜索”标志是否为“搜索过”,如果是则执行第4步,如果不是则判断其有无右孩子,没有则照样执行第4步,有则置标记为“搜索过”,并将右孩子入栈后执行第2步。
四、二叉树非递归遍历代码实现
# encoding=utf-8
# 输入形式为二叉树的完全二叉树形式的层次遍历序列列表(空节点处以-1代替)
import tools
class solution():
def __init__(self, bitree):
self.pst = ''
self.ist = ''
self.post = ''
self.preorder(bitree)
self.inorder(bitree)
self.postorder(bitree)
def preorder(self, tree):
if tree[0] == -1 : return 0
stack = tools.Stack()
i = 0
stack.push(i)
while True :
self.pst += str(tree[i])
i = (i + 1) * 2 - 1
if i < len(tree) and tree[i] != -1 :
stack.push(i)
continue
while True :
i = (stack.pop()+1) * 2
if i < len(tree) and tree[i] != -1 :
stack.push(i)
break
else :
if stack.is_empty() :
return 0
else:
i = stack.top()
continue
def inorder(self, tree):
if tree[0] == -1 : return 0
stack = tools.Stack()
i = 0
stack.push(i)
while True :
i = (i + 1) * 2 - 1
if i < len(tree) and tree[i] != -1 :
stack.push(i)
continue
while True :
i = stack.pop()
self.ist += str(tree[i])
i = (i + 1) * 2
if i < len(tree) and tree[i] != -1 :
stack.push(i)
break
else :
if stack.is_empty() :
return 0
else:
i = stack.top()
continue
def postorder(self, tree):
if tree[0] == -1: return 0
flag = [0] * len(tree)
stack = tools.Stack()
i = 0
stack.push(i)
while True:
if (i + 1) * 2 - 1 < len(tree) and tree[(i + 1) * 2 - 1] != -1:
i = (i + 1) * 2 - 1
stack.push(i)
continue
elif (i + 1) * 2 < len(tree) and tree[(i + 1) * 2] != -1:
flag[i] = 1
i = (i + 1) * 2
stack.push(i)
continue
else:
while True:
self.post += str(tree[i])
stack.pop()
if stack.is_empty():
return 0
else:
i = stack.top()
if flag[i] == 1:
continue
elif (i + 1) * 2 < len(tree) and tree[(i + 1) * 2] != -1:
flag[i] = 1
stack.push((i + 1) * 2)
i = (i + 1) * 2
break
else:
continue
if __name__ == '__main__':
solu = solution([6, 3, 7, 2, 5, -1, 8])
print(solu.pst)
print(solu.ist)
print(solu.post)
这里顺便附上Python下定义的栈结构及基本操作实现代码(文件名为tools.py):
class Stack(object):
def __init__(self):
self.__list = []
def is_empty(self):
return self.__list == []
def push(self,item):
self.__list.append(item)
def pop(self):
if self.is_empty():
return
else:
return self.__list.pop()
def top(self):
if self.is_empty():
return
else:
return self.__list[-1]
执行上述代码后,输出结果为:
输出结果和上文相同,表明我们的思想基本正确。