tree traversal (树的遍历) - postorder traversal (后序遍历)

tree traversal (树的遍历) - postorder traversal (后序遍历)

1. tree traversal - 树的遍历

二叉树的遍历 (traversing binary tree) 是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。遍历是将二叉树中的结点信息由非线性排列变为某种意义上的线性排列,遍历操作使非线性结构线性化。

前序遍历 (preorder) - 中序遍历 (inorder) - 后序遍历 (postorder) - 层序遍历 (breadth-first)。

二叉树由根结点、左子树和右子树三部分组成。假设 D、L、R 分别代表遍历根结点、遍历左子树、遍历右子树,则二叉树的遍历方式有 6 种:DLR、DRL、LDR、LRD、RDL、RLD。先遍历左子树和先遍历右子树在算法设计上没有本质区别,如果我们限制了从左到右的习惯方式,那么主要就分为前序遍历 (preorder) - 中序遍历 (inorder) - 后序遍历 (postorder) - 层序遍历 (breadth-first) 四种。

1. 深度优先搜索 (depth-first search,DFS)

二叉树的深度优先遍历可细分为前序遍历、中序遍历、后序遍历。这三种遍历可以用递归实现,也可使用迭代实现。

  1. DLR - 前序遍历 (根在前,从左往右,一棵树的根永远在左子树前面,左子树永远在右子树前面)。
  2. LDR - 中序遍历 (根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)。
  3. LRD - 后序遍历 (根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)。

前序遍历 (preorder) - 中序遍历 (inorder) - 后序遍历 (postorder) 是针对根节点而言的,左右子树的遍历顺序不变,前序就是根节点最先遍历,然后左右子树。中序是把根节点放在中间遍历。后序是把根节点放在最后遍历。

在这里插入图片描述

  1. 根是相对的,对于整棵树而言只有一个根,但对于每棵子树而言,又有自己的根。对于整棵树而言,A 是根,A 分别在前面、中间、后面被遍历到。对于 D,它是 G 和 H 的根。对于 D、G、H 这棵树而言,三种排序顺序分别是 DGH、GDH、GHD。对于 C,它是 E 和 F 的根,三种排序顺序分别是 CEF、ECF、EFC。
  2. 整棵树的起点,从 A 开始,前序遍历的话,一棵树的根永远在左子树前面,左子树又永远在右子树前面。
  3. 二叉树结点的先根序列、中根序列和后根序列中,所有叶子结点的先后顺序一样。

2. 广度优先搜索 - 宽度优先搜索 - 横向优先搜索 (breadth-first search,BFS)

层序遍历 (breadth-first)。是按层从上到下,从左到右遍历。

在这里插入图片描述

2. 后序遍历 (postorder)

二叉树有三类节点:父节点,左孩子节点,右孩子节点。前序遍历是先遍历父节点,之后左孩子节点,右孩子节点。中序和后序遍历都是针对父节点的,父节点中间遍历,父节点最后遍历。左右孩子节点的遍历顺序不变。

在这里插入图片描述

遍历顺序是左孩子节点,右孩子节点,根节点。遍历结果是 3,4,1,7,5,6,2,0

扫描二维码关注公众号,回复: 10302829 查看本文章
  1. 节点 0 的左孩子和右孩子分别如下图示。
    在这里插入图片描述

节点 0 的左孩子 A,节点 0 的右孩子 B。左、右孩子又是一颗单独的树 A,B。

  1. 后续遍历 A 为 3,4,1

  2. B 的左子树,还可以再次拆分为 C。
    在这里插入图片描述

在这里插入图片描述

  1. 遍历 C 得 7,5。B 的左子树 C 结束,然后遍历 B 的右孩子节点和根节点。B 遍历结果是 7,5,6,2

  2. 最后后续遍历整棵树 A - B - 0,遍历结果是 (3,4,1),(7,5,6,2),0 = 3,4,1,7,5,6,2,0

3. 迭代实现后序遍历 (postorder)

(1) 与前序中序遍历一样,后序遍历也要用一个栈,stack 循环输入左孩子节点。
(2) 当遍历到左孩子节点为空时,弹栈输出。有右孩子节点时,从右孩子节点开始循环入栈节点 (持续 (1) 步骤)。
(3) 需要记录一个 last_print_node 记录上一次打印的节点。对比弹栈的节点的右孩子是否已经输出过,需不需要再次入栈。

  1. 从根节点 0 开始,持续遍历左节点,入栈。
    在这里插入图片描述

  2. 弹出栈元素 3,判断 3 没有右孩子节点,输出,回到节点 1。
    在这里插入图片描述

  3. 弹出栈元素节点 1,判断节点 1 有右孩子节点 4,节点 1 再次入栈,持续遍历 4 的左节点入栈,直到为空。
    在这里插入图片描述

  4. 弹出栈元素节点 4,节点 4 无右孩子节点,输出节点 4,回到节点 1。
    在这里插入图片描述

  5. 弹出栈元素节点 1,此时 1 有右孩子节点,我们在上一步骤中,有右孩子节点的会让右孩子节点入栈,但是右孩子节点已经输出了。记录上一次遍历的节点 last_print_node,判断 last_print_node 是否等于 1 的右孩子节点。后续遍历的父节点一定是最后一个遍历,节点 1 的右孩子节点 4 一定是在它本身的树结构中最后一个遍历的。所以判断 last_print_node == 节点 1 的右孩子节点。已经输出,那么弹出节点1,输出。
    在这里插入图片描述

  6. 弹出节点 0,判断节点 0 有右孩子节点,且 last_print_node != 节点 0 的右孩子节点。入栈节点 0,持续左孩子遍历 2,5,7 入栈。
    在这里插入图片描述

  7. 弹出节点 7,5,判断无右孩子节点,输出。
    在这里插入图片描述

  8. 弹出节点 2,判断有右孩子节点,入栈 2,入栈 6。
    在这里插入图片描述

  9. 弹出节点 6,输出,last_print_node 更新为节点 6。

  10. 弹出节点 2,判断 2 的右孩子节点 == last_print_node,输出节点 2。更新 last_print_node 为节点 2。
    在这里插入图片描述

  11. 弹出节点 0,对比节点 0 的右孩子节点 == last_print_node,输出遍历结果为 3,4,1,7,5,6,2,0

References

https://dsa.cs.tsinghua.edu.cn/~deng/ds/dsacpp/
https://www.programiz.com/dsa/tree-traversal
http://btechsmartclass.com/data_structures/binary-tree-traversals.html
https://me.csdn.net/fanfan199312

发布了525 篇原创文章 · 获赞 1873 · 访问量 116万+

猜你喜欢

转载自blog.csdn.net/chengyq116/article/details/105073557