Algorithm postOrder(T, p): for each child c in T.children(p) do postOrder(T, c) visit(p)
可以递归理解:对每一个节点,逐步进行如下操作
后序遍历左子树
后序遍历右子树
访问根节点
例如:反向打印章节
1.1.3 二叉树的中序遍历
在中序遍历中,我们通过递归遍历左右子树后再访问根节点。伪代码:
1 2 3 4 5 6
Algorithm inOrder(T, p): if p has a left child lc then inOrder(T, lc) visit(p) if p has a right child rc then inOrder(T, rc)
可以递归理解:对每一个节点,逐步进行如下操作
中序遍历左子树
访问根节点
中序遍历右子树
例如:表达式树
1.1.4 算法分析
前/中/后序遍历均为深度优先遍历算法,可用递归实现,也可用模拟递归栈的方式进行非递归实现。
递归栈的最大深度和树的深度保持一致:
最好情况,空间复杂度 O(log n) 。
最坏情况,空间复杂度 O(n) 。
因为是遍历,所以时间复杂度为 O(n) 。
1.2 广度优先:层序遍历
用广度优先遍历算法,即层序遍历算法。在访问深度 d 的位置之前先访问深度 d+1 的位置。按照层次自低向高,每层从左向右访问。伪代码:
1 2 3 4 5 6 7
Algorithm breadthfirst(T): Initialize queue Q to contain T.root() while Q not empty do p = Q.dequeue() visit(p) for each child c in T.children(p) do Q.enqueue(c)
无法用递归实现,借助队列理解:
先将根节点入队
每出队一个节点,将其孩子节点依次放入队列
例如:下面实现了对一个树的逐步层序遍历。
2 Python 实现树遍历
首先,继续上一章 树与二叉树 定义的 Tree 类进行补充。先定义 __iter__ 方法,产生迭代器,其中的 positions() 方法就可以用于指代不同的遍历方式。这个迭代器只是可以使用 for i in obj 的方式直接获取 element 值,而非 Position 节点类。
1 2 3 4
def__iter__(self): """定义迭代器:遍历方式可选""" for p inself.positions(): # positions() 可选不同的遍历方式 yield p.element()
2.1 前序遍历
在 Tree 类后继续补充 preorder 方法和 _subtree_preorder 方法
1 2 3 4 5 6 7 8 9 10 11 12
defpreorder(self): """前序遍历""" ifnotself.is_empty(): for p inself._subtree_preorder(self.root()): # 递归实现 yield p
def_subtree_preorder(self, p): """前序遍历子树""" yield p # 访问根节点 for c inself.children(p): # 遍历子树 for other inself._subtree_preorder(c): yield other
classInorderTree(BinaryTree): # ---------------- 遍历算法 ---------------- # 迭代器 def__iter__(self): """定义迭代器:遍历方式可选""" for p inself.positions(): # positions() 可选不同的遍历方式 yield p.element()
# 前序遍历 definorder(self): """中序遍历""" ifnotself.is_empty(): for p inself._subtree_inorder(self.root()): # 递归实现 yield p
def_subtree_inorder(self, p): """中序遍历子树""" ifself.left(p) isnotNone: # 遍历左子树 for other inself._subtree_inorder(self.left(p)): yield other yield p # 访问根节点 ifself.right(p) isnotNone: # 遍历右子树 for other inself._subtree_inorder(self.right(p)): yield other
whilenot fringe.is_empty(): p = fringe.dequeue() # 取出头部 yield p # 生成
for c inself.children(p): # 将子节点入队 fringe.enqueue(c)
3 树的遍历的应用
3.1 前序遍历:目录表
树的前序遍历可以自然地被用于产生文档或书籍的目录表:
如不需要缩进,则可直接使用前序遍历打印目录表
如需要缩进,则需要定义一个特殊的前序遍历函数
代码实现
1 2 3 4 5 6 7 8 9
defpreorder_indent(T, p, d): """前序遍历:打印目录 :param T: 目录树 :param p: 当前节点 :param d: 记录深度 """ print(2 * d * ' ' + str(p.element())) # 记录深度 for c in T.children(p): preorder_indent(T, c, d + 1) # 递归打印子树
3.2 后序遍历:计算磁盘空间
计算磁盘空间:
计算磁盘空间需要将文件系统表示为树后,使用后序遍历
需要定义一个特殊的后序遍历函数记录当前占有的存储空间
代码实现
1 2 3 4 5 6 7 8 9
defdisk_space(T, p): """计算文件系统树,p 节点后的总磁盘空间 :param T: 文件系统树 :param p: 当前节点 """ subtotal = p.element().space() # 节点 p 占有的空间 for c in T.children(p): # 计算 p 的子树总空间 subtotal += disk_space(T, c) # 递归计算子树空间 return subtotal
3.3 中序遍历:打印表达式
表达式树是一棵二叉树,使用表达式树输出表达式需要一种特殊的中序遍历算法:
访问节点时输出节点存储的值或运算符
遍历左子树前输出 (
遍历右子树后输出 )
伪代码
1 2 3 4 5 6 7 8
Algorithm printExpression(v): if v has a left child print("(") printExpression(left(v)) print(v.element()) if v has a right child printExpression(right(v)) print (")")
3.4 后序遍历:计算表达式
使用表达式树计算表达式的值需要一种特殊的后序遍历:
使用递归返回子树的值
访问内部节点时,使用内部节点的运算符对左、右子树的值做运算
伪代码
1 2 3 4 5 6 7 8
Algorithm evalExpr(v): if is_leaf (v) return v.element() else x = evalExpr(left(v)) y = evalExpr(right(v)) op = operator stored at v return x y