二叉树遍历(先序、中序、后序)

二叉树的遍历(递归与非递归)

遍历:traversal  递归:recursion

栈----------回溯----------递归

栈和回溯有关

本文讨论二叉树的常见遍历方式的代码(Java)实现,包括

前序(preorder)、中序(inorder)、后序(postorder)、层序(level order),

进一步考虑递归非递归的实现方式。

递归的实现方法相对简单,但由于递归的执行方式每次都会产生一个新的方法调用栈,如果递归层级较深,会造成较大的内存开销,

相比之下,非递归的方式则可以避免这个问题。递归遍历容易实现,非递归则没那么简单,非递归调用本质上是通过维护一个栈,模拟递归调用的方法调用栈的行为。

                    二叉树遍历 by Java

二叉树有多种遍历方法,深度优先遍历、广度优先遍历(层次遍历)

本文只涉及二叉树的先序、中序、后序的递归和非递归遍历。

涉及到的代码都用Java编写。

首先给出二叉树节点类:

树节点:

 1 class TreeNode {
 2     int val;
 3     //左子树
 4     TreeNode left;
 5     //右子树
 6     TreeNode right;
 7     //构造方法
 8     TreeNode(int x) {
 9         val = x;
10     }
11 }

无论是哪种遍历方法,考查节点的顺序都是一样的(思考做试卷的时候,人工遍历考查顺序)。只不过有时候考查了节点,将其暂存,需要之后的过程中输出。

              图2:先序、中序、后序遍历节点考查顺序

如图1所示,三种遍历方法(人工)得到的结果分别是:

先序:1 2 4 6 7 8 3 5
中序:4 7 6 8 2 1 3 5
后序:7 8 6 4 2 5 3 1

三种遍历方法的考查顺序一致,得到的结果却不一样,原因在于:

先序:考察到一个节点后,即刻输出该节点的值,并继续遍历其左右子树。(根左右)

中序:考察到一个节点后,将其暂存,遍历完左子树后,再输出该节点的值,然后遍历右子树。(左根右)

后序:考察到一个节点后,将其暂存,遍历完左右子树后,再输出该节点的值。(左右根)


先序遍历

递归先序遍历

递归先序遍历很容易理解,先输出节点的值,再递归遍历左右子树。中序和后序的递归类似,改变根节点输出位置即可。

1 // 递归先序遍历
2 public static void recursionPreorderTraversal(TreeNode root) {
3     if (root != null) {
4         System.out.print(root.val + " ");
5         recursionPreorderTraversal(root.left);
6         recursionPreorderTraversal(root.right);
7     }
8 }

非递归先序遍历

因为要在遍历完节点的左子树后接着遍历节点的右子树,为了能找到该节点,需要使用栈来进行暂存。中序和后序也都涉及到回溯,所以都需要用到栈。

              图2:非递归先序遍历

遍历过程参考注释

 1 // 非递归先序遍历
 2 public static void preorderTraversal(TreeNode root) {
 3     // 用来暂存节点的栈
 4     Stack<TreeNode> treeNodeStack = new Stack<TreeNode>();
 5     // 新建一个游标节点为根节点
 6     TreeNode node = root;
 7     // 当遍历到最后一个节点的时候,无论它的左右子树都为空,并且栈也为空
 8     // 所以,只要不同时满足这两点,都需要进入循环
 9     while (node != null || !treeNodeStack.isEmpty()) {
10         // 若当前考查节点非空,则输出该节点的值
11         // 由考查顺序得知,需要一直往左走
12         while (node != null) {
13             System.out.print(node.val + " ");
14             // 为了之后能找到该节点的右子树,暂存该节点
15             treeNodeStack.push(node);
16             node = node.left;
17         }
18         // 一直到左子树为空,则开始考虑右子树
19         // 如果栈已空,就不需要再考虑
20         // 弹出栈顶元素,将游标等于该节点的右子树
21         if (!treeNodeStack.isEmpty()) {
22             node = treeNodeStack.pop();
23             node = node.right;
24         }
25     }
26 }

先序遍历结果:

递归先序遍历: 1 2 4 6 7 8 3 5
非递归先序遍历:1 2 4 6 7 8 3 5

中序遍历

递归中序遍历

过程和递归先序遍历类似

1 // 递归中序遍历
2 public static void recursionMiddleorderTraversal(TreeNode root) {
3     if (root != null) {
4         recursionMiddleorderTraversal(root.left);
5         System.out.print(root.val + " ");
6         recursionMiddleorderTraversal(root.right);
7     }
8 }

非递归中序遍历

和非递归先序遍历类似,唯一区别是考查到当前节点时,并不直接输出该节点。

而是当考查节点为空时,从栈中弹出的时候再进行输出(永远先考虑左子树,直到左子树为空才访问根节点)。

 1 // 非递归中序遍历
 2 public static void middleorderTraversal(TreeNode root) {
 3     Stack<TreeNode> treeNodeStack = new Stack<TreeNode>();
 4     TreeNode node = root;
 5     while (node != null || !treeNodeStack.isEmpty()) {
 6         while (node != null) {
 7             treeNodeStack.push(node);
 8             node = node.left;
 9         }
10         if (!treeNodeStack.isEmpty()) {
11             node = treeNodeStack.pop();
12             System.out.print(node.val + " ");
13             node = node.right;
14         }
15     }
16 }

中序遍历结果

递归中序遍历: 4 7 6 8 2 1 3 5
非递归中序遍历:4 7 6 8 2 1 3 5

后序遍历

递归后序遍历

过程和递归先序遍历类似

1 // 递归后序遍历
2 public static void recursionPostorderTraversal(TreeNode root) {
3     if (root != null) {
4         recursionPostorderTraversal(root.left);
5         recursionPostorderTraversal(root.right);
6         System.out.print(root.val + " ");
7     }
8 }

非递归后序遍历

后续遍历和先序、中序遍历不太一样。

后序遍历在决定是否可以输出当前节点的值的时候,需要考虑其左右子树是否都已经遍历完成。

所以需要设置一个lastVisit游标

若lastVisit等于当前考查节点的右子树,表示该节点的左右子树都已经遍历完成,则可以输出当前节点。

并把lastVisit节点设置成当前节点,将当前游标节点node设置为空,下一轮就可以访问栈顶元素。

否者,需要接着考虑右子树,node = node.right。

以下考虑后序遍历中的三种情况:

          图3:后序,右子树不为空,node = node.right

如图3所示,从节点1开始考查直到节点4的左子树为空。

注:此时的游标节点node = 4.left == null。

此时需要从栈中查看 Peek()栈顶元素。

发现节点4的右子树非空,需要接着考查右子树,4不能输出,node = node.right。

          图4:后序,左右子树都为空,直接输出

如图4所示,考查到节点7(7.left == null,7是从栈中弹出),其左右子树都为空,可以直接输出7。

此时需要把lastVisit设置成节点7,并把游标节点node设置成null,下一轮循环的时候会考查栈中的节点6。

            图5:后序,右子树 = lastVisit,直接输出

如图5所示,考查完节点8之后(lastVisit == 节点8),将游标节点node赋值为栈顶元素6,节点6的右子树正好等于节点8。表示节点6的左右子树都已经遍历完成,直接输出6。

此时,可以将节点直接从栈中弹出Pop(),之前用的只是Peek()。

将游标节点node设置成null。

 1 // 非递归后序遍历
 2 public static void postorderTraversal(TreeNode root) {
 3     Stack<TreeNode> treeNodeStack = new Stack<TreeNode>();
 4     TreeNode node = root;
 5     TreeNode lastVisit = root;
 6     while (node != null || !treeNodeStack.isEmpty()) {
 7         while (node != null) {
 8             treeNodeStack.push(node);
 9             node = node.left;
10         }
11         //查看当前栈顶元素
12         node = treeNodeStack.peek();
13         //如果其右子树也为空,或者右子树已经访问
14         //则可以直接输出当前节点的值
15         if (node.right == null || node.right == lastVisit) {
16             System.out.print(node.val + " ");
17             treeNodeStack.pop();
18             lastVisit = node;
19             node = null;
20         } else {
21             //否则,继续遍历右子树
22             node = node.right;
23         }
24     }
25 }

后序遍历结果

递归后序遍历: 7 8 6 4 2 5 3 1
非递归后序遍历:7 8 6 4 2 5 3 1

Test:

package BinaryTreeTraversal;

public class Test {
    /*
     *                                 1
     *                                /       *                               2    3
     *                              /           *                             4        5
     *                                   *                               6
     *                              /       *                             7      8
     */
    public static void main(String[] args) {
        TreeNode root=new TreeNode(1);
        root.left=new TreeNode(2);
        root.right=new TreeNode(3);
        root.left.left=new TreeNode(4);
        root.right.right=new TreeNode(5);
        root.left.left.right=new TreeNode(6);
        root.left.left.right.left=new TreeNode(7);
        root.left.left.right.right=new TreeNode(8);

        System.out.println("preorder:");
        PreorderTraversal.recursionPreorderTraversal(root);
        System.out.println();
        PreorderTraversal.preorderTraversal(root);

        System.out.println("\n");

        System.out.println("inorder:");
        InorderTraversal.recursionInorderTraversal(root);
        System.out.println();
        InorderTraversal.inorderTraversal(root);

        System.out.println("\n");

        System.out.println("postorder:");
        PostorderTraversal.recursionPostorderTraversal(root);
        System.out.println();
        PostorderTraversal.postorderTraversal(root);

    }
}

转载连接:二叉树遍历(先序、中序、后序)

原文地址:https://www.cnblogs.com/midiyu/p/8542333.html

时间: 2024-10-12 23:51:27

二叉树遍历(先序、中序、后序)的相关文章

Java数据结构四之——二叉树的前、中、后序遍历

程序来自Program Creek 前 Preorder binary tree traversal is a classic interview problem about trees. The key to solve this problem is to understand the following: What is preorder? (parent node is processed before its children) Use Stack from Java Core lib

给定二叉树的先序遍历和中序遍历,输出它的后序遍历序列

这里没再用到先申请大Node数组的方法了,想练练写动态内存分配和释放的,一次OK了,也没怎么出错啊,开心~ 方法二 - Code: //给出一个二叉树的先序遍历和中序遍历,输出它的后序遍历 //直接构造的方法白书已给出.这里是先递归构造二叉树,然后进行后序遍历. #include<stdio.h> #include<string.h> #include<stdlib.h> #define MAXN 1000 typedef struct node { char data

华南理工数据结构大作业第二题 二叉树各种操作深度结点个数后序前序中序层次求祖先

/*#include<iostream> #include<windows.h> using namespace std ; struct BTNode { char data ; BTNode *left ; BTNode *right ; BTNode () { left = NULL ; right = NULL ; } } ; int main () { cout <<"题目所给的二叉树用括号表示法后表示为:A(B(D,E(H(J,K(L,M(,N))

算法进化历程之“根据二叉树的先序和中序序列输出后序序列”

巧若拙(欢迎转载,但请注明出处:http://blog.csdn.net/qiaoruozhuo) 前不久在看到一个作业"根据二叉树的先序和中序序列输出后序序列",当时我参考<数据结构与算法(C语言)习题集>上的做法,先根据先中序序列确定一颗二叉树,然后后序遍历二叉树输出后序序列. 函数采用了递归算法,利用函数传入的先序和中序序列的左右边界,确定要处理的序列段,生成相应的二叉树. 基本思路是,把该段先序序列的第一个元素作为当前二叉树的根结点,然后在中序序列找到根结点.根结点

通过二叉树的中序序列和后序序列获取前序序列

二叉树的遍历方式常见的三种是:先序遍历(ABC).中序遍历(BAC).后序遍历(BCA) 先序遍历: 若二叉树为空,则空操作:否则: 访问根结点; 先序遍历左子树: 先序遍历右子树. 中序遍历: 若二叉树为空,则空操作:否则: 中序遍历左子树: 访问根结点: 中序遍历右子树. 后序遍历: 若二叉树为空,则空操作:否则: 后序遍历左子树: 后序遍历右子树: 访问根结点. 在学习到 根据遍历序列确定二叉树 时,知道了:可以通过二叉树的先中或者中后遍历序列唯一确定一棵二叉树. 根据算法描述 使用jav

先序序列和后序序列并不能唯一确定二叉树

数据结构的基础知识中重要的一点就是能否根据两种不同遍历序列的组合(有三种:先序+中序,先序+后序,中序+后序),唯一的确定一棵二叉树.然后就是根据二叉树的不同遍历序列(先序.中序.后序),重构二叉树.显然,这三种组合并不是都能唯一确定二叉树的,其中先序+后序就不能唯一确定一棵二叉树,其他两种组合可以唯一的确定一颗二叉树. 由先序序列和后序序列不能唯一确定一棵二叉树,因无法确定左右子树两部分. 反例:任何结点只有左子树的二叉树和任何结点只有右子树的二叉树,其前序序列相同,后序序列相同,但却是两棵不

中序表达式转后序表式式

中序表达式转后序表式式: 将中序表达式所有括号补全,然后将所有运算符向右移出无匹配的第一个右括号,去掉括号即为后序表式式 举例: 原式:a+b*(c+d/e) 补全括号:(a+(b*(c+(d/e)))) 操作符右移:(a(b(c(de)/)+)*)+ 去掉括号:abcde/+*+ 中序表达式转前序表式式: 将中序表达式所有括号补全,然后将所有运算符向左移出无匹配的第一个左括号,去掉括号即为前序表式式 举例: 原式:a+b*(c+d/e) 补全括号:(a+(b*(c+(d/e)))) 操作符右移

二叉树遍历算法——包含递归前、中、后序和层次,非递归前、中、后序和层次遍历共八种

首先,要感谢网上的参考资料. http://mengliao.blog.51cto.com/876134/1178079(作者:BlackAlpha) http://blog.csdn.net/fzh1900/article/details/14056735(作者:_云淡风轻) http://blog.csdn.net/stpeace/article/details/8138458(作者:stpeace) 二叉树是使用的比较广泛的一种数据结构,这里我写了二叉树的相关操作,包括初始化.新建.以及遍

日常学习随笔-用链表的形式实现普通二叉树的新增、查找、遍历(前、中、后序)等基础功能(侧重源码+说明)

一.二叉树 1.二叉树的概念 二叉树是每个节点最多有两个子树的树结构.通常子树被称作"左子树"(left subtree)和"右子树"(right subtree),其次序不能任意颠倒. 2.性质 (1)若二叉树的层次从0开始,则在二叉树的第i层至多有2^i个结点(i>=0): (2)高度为k的二叉树最多有2^(k+1) - 1个结点(k>=-1). (空树的高度为-1): (3)对任何一棵二叉树,如果其叶子结点(度为0)数为m, 度为2的结点数为n,

HDU 1710Binary Tree Traversals(已知前序中序,求后序的二叉树遍历)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1710 解题思路:可以由先序和中序的性质得到 : 先序的第一个借点肯定是当前子树的根结点, 那么在 中序中找到这个结点, 则这个结点左边的节点属于左子树, 右边的属于右子树.然后递归遍历就可以了. 样例: 9 1 2 4 7 3 5 8 9 6 4 7 2 1 8 5 9 3 6 7 4 2 8 9 5 6 3 1 如图: 因此,用深搜就能轻松解决了,注意DFS中的变量,以及向清楚DFS的条件,这题就