js递归遍历树结构_我是怎么调试出来二叉树的遍历(超精彩配图),从此遍历不再愁了

推荐学习

  • 牛掰!“基础-中级-高级”Java程序员面试集结,看完献出我的膝盖
  • 数据结构与算法,程序员必过的坎?不掌握一定挤不进BATJ的神技?

我是怎么调试出来二叉树的遍历(超精彩配图),从此遍历不再愁了!

1f65f32d04bd9d7356f4baa261864bbc.png

前言

二叉树遍历(Traversing binary tree)是指从根节点触发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被依次访问且仅仅被访问一次。

二叉树的遍历有四种方式,分别为:先序遍历、中序遍历、后序遍历、层次遍历。

一、准备工作

在做实验之前先准备一个二叉树的结构,这样方便进行实验。

(一)定义二叉树的数据结构

package tree;/** * @Auther: truedei * @Date: 2020 /20-6-5 11:59 * @Description: */public class TreeNode {        int val;//每个节点存放的数据        TreeNode left;//左节点        TreeNode right;//右节点        TreeNode(int x) { val = x; }}

(二)手工构造一个二叉树

至于为什么是手工,因为不想把你搞混了,这篇文章主要是遍历,对于构造二叉树的方法可以自行研究一下。

package tree;/** * @Auther: truedei * @Date: 2020 /20-6-7 17:39 * @Description: */public class TestTrueDeiTree {    public static void main(String[] args) {        TreeNode t1 = new TreeNode(3);        TreeNode t2 = new TreeNode(9);        TreeNode t3 = new TreeNode(20);        TreeNode t4 = new TreeNode(15);        TreeNode t5 = new TreeNode(7);        t1.left=t2;        t1.right=t3;        t3.left=t4;        t3.right=t5;    }}

构造之后的二叉树的结构图:

0b245d7c3eb10051cc86d4dcdb023959.png

二、二叉树的遍历讲解

接下来我们这几种遍历算法,一个一个的认识。

四种遍历的概念:

(1)先序遍历:先访问根节点,再访问左子树,最后访问右子树;可以说为”根-左-右“。

(2)后序遍历:先左子树,再右子树,最后根节点;可以说为”左-右-根“。

(3)中序遍历:先左子树,再根节点,最后右子树;可以说为”左-根-右“。

(4)层序遍历:每一层从左到右访问每一个节点。

(一)前序遍历

1、前序遍历概念

前序遍历:先访问根节点,再访问左子树,最后访问右子树;可以说为”根-左-右“。

按照:先访问根节点,再访问左子树,最后访问右子树的顺序如果用图来描述一下的话,是这样子的;

2、图解前序遍历

1、首先我们有一颗如下所示的二叉树,这就是我们的初始值:

eb732d96976bf51ad217049323b759cf.png

方便理解,为此我做了一个GIF的图:

ac17264c6a1b7a057290585e3d3f32fd.gif

遍历的顺序:

ce72532abc73d4d04332ee89ea6fb169.png

通过这个规则我们得到前序遍历结果:3、9、20、15、7

3、代码实现前序遍历

(1)递归实现

/** 根-> 左-> 右 * 前序遍历 * @param root */public static void PreOrder(TreeNode root) {      //结束的条件    if (root==null)        return;    System.out.print(root.val+" ");    //使用递归进行遍历左孩子    PreOrder(root.left);    //递归遍历右孩子    PreOrder(root.right);}

结果:

3 9 20 15 7

(2)非递归实现

1.首先申请一个新的栈,记为stack;

2.声明一个结点treeNode,让其指向root结点;

3.如果treeNode的不为空,将treeNode的值打印,并将treeNode入栈,然后让treeNode指向4.treeNode的左结点,

5.重复步骤3,直到treenode为空;

6.然后出栈,让treeNode指向treeNode的右孩子

7.重复步骤3,直到stack为空.

/**根-->左-->右* 非递归前序遍历* * * 1,首先申请一个新的栈,记为stack;* 2,声明一个结点treeNode,让其指向node结点;* 3,如果treeNode的不为空,将treeNode的值打印,并将treeNode入栈,然后让treeNode指向treeNode的左结点,* 4,重复步骤3,直到treenode为空;* 5,然后出栈,让treeNode指向treeNode的右孩子* 6,重复步骤3,直到stack为空.** @param root*/public static  void preOrderStack(TreeNode root){    //利用栈的特性(先进的后出),用于存储节点数据,一层一层的往上冒    Stack stack = new Stack<>();    //相当于临时的变量,记录当前的所在节点    TreeNode treeNode = root;    //两个条件满足一个即可继续执行,退出的条件是当前的节点为null和栈也空了,说明没有数据了    //栈空了说明冒到了最上一层,最上一层也遍历成了,就空了    while (treeNode != null || !stack.isEmpty()){        //迭代访问节点的左孩子,并入栈        //一直遍历最左边的节点        while (treeNode != null){            //先根            System.out.print(treeNode.val+" ");            stack.push(treeNode);            //遍历完根,然后还是遍历最左边的。。如果还有的话,还是遍历最左边的            treeNode  = treeNode.left;        }        //如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子        if(!stack.isEmpty()){            treeNode = stack.pop();            treeNode = treeNode.right;        }    }}

4、使用IDEA调试java代码进一步查看执行过程

现在我的代码是这样子的:

package tree;/** * @Auther: truedei * @Date: 2020 /20-6-7 17:39 * @Description: */public class TestTrueDeiTree {    public static void main(String[] args) {        TreeNode t1 = new TreeNode(3);        TreeNode t2 = new TreeNode(9);        TreeNode t3 = new TreeNode(20);        TreeNode t4 = new TreeNode(15);        TreeNode t5 = new TreeNode(7);        t1.left=t2;        t1.right=t3;        t3.left=t4;        t3.right=t5;        PreOrder(t1);    }    /**     * 前序遍历     * @param root     */    public static void PreOrder(TreeNode root) {  //先序遍历        if (root==null)            return;        System.out.print(root.val+" ");        //使用递归进行遍历左孩子        PreOrder(root.left);        //递归遍历右孩子        PreOrder(root.right);    }}//树结构class TreeNode {    int val;//每个节点存放的数据    TreeNode left;//左节点    TreeNode right;//右节点    TreeNode(int x) { val = x; }}

为了方便调试,我们先在下面这几个地方打上断点

9fd75e7169f9f2bc4b32cae44b83f4d8.png

第1次:

因为是初次遍历,所以root肯定不为null,所以执行打印的这条语句;

f417680126f9ac7ed6ef5c6119ff6feb.png

打印了3,就是我们想要的结果

df2647f15e1a18ce3cd98335b28599cd.png

继续调试,更详细的解析,就看图吧:

c9bed58a17bc364ea1247bc8d7d5d127.png

继续下一步

999883bbddfaec911cce8c87945787bf.png
bf85a830c364a803d6ad26a38facb730.png

再次点击下一步之后,传入的是null,所以就不会再继续执行剩下的代码了

e152b8809206ba9d071224e499381d5e.png

继续点击下一步:

至于为什么会调用执行了遍历右孩子的代码。原因有:

1、程序的代码都是顺序执行的

2、和JVM相关,如果你学过JVM,则这个地方为什么会执行就会很清晰了

3、遍历3的左孩子的结束了,自然而然的销毁了之前的代码,然后就执行到了

09dd44e0e1881d961d3dee5b3a00a010.png
e465db263ead627f71dcd2ee9c13ccdc.png
9b55592c0d2f46e75aceb5cf2753f3f5.png

继续点下一步,会又一次的进入,并且把20的left节点传入

f9024e0facab4929f90f0d89f7635455.png

这个应该就不用多解释了,上面解释的很清楚了

3b24766bf964054f4f97d7b6522ed655.png

继续下一步会打印出当前节点的值,并调到下一个Debug处

f35da16a264d9776285477fb634fbc16.png

继续下一步会为null,所以退出当前的方法

0846563917d9a493b62e5c526b6452b2.png
f9262567084f81114e9c678d5bd2b51e.png

发现还是为null

2537f790419de71d61bfdb569b6ad512.png

再次点击下一步,就会结束掉这次的了

9f7ceafde52926f32c3077c6d159755a.png

再次点击下一步,会销毁掉刚才运行的方法,回到上一次进入时的状态,这次就该轮到right了

7784fcf8a620e15ac80ec9b961bd5d41.png
c3c904d6107a5c5cfff7d49598181e51.png
75733240245241f36b7abe4896f7e271.png
08961503f100c5b10771783f7c247ec3.png

再点一次,main方法也会执行结束,并将其销毁,从而得到了我们想要的结果:

7730603de10e640499882008528d9207.png

(二)中序遍历

1、中序遍历概念

中序遍历:先左子树,再根节点,最后右子树;可以说为”左-根-右“。

按照:先左子树,再根节点,最后右子树的顺序如果用图来描述一下的话,是这样子的;

2、图解中序遍历

1、首先我们有一颗如下所示的二叉树,这就是我们的初始值:

446f96685f1446def5dc91080c837185.png

执行顺序:

4dd3c130b421f378800c660e0d1c11fd.png
f25a418ae03b05b33de7c0ab4adf76dd.gif

3、代码实现中序遍历

(1)递归实现

因为是递归,代码也很简单,修改一下位置就好了。

/** 左-> 根-> 右  * 中序遍历  * @param root */public static void MidOrder(TreeNode root) {  //先序遍历    if (root==null)        return;    //使用递归每次都先进行遍历左孩子    MidOrder(root.left);    System.out.print(root.val+" ");    //递归遍历右孩子    MidOrder(root.right);}

(2)非递归实现

1.申请一个新栈,记为stack,申请一个变量treeNode,初始时令treeNode为头节点;

2.先把treeNode节点压入栈中,对以treeNode节点为头的整棵子树来说,依次把整棵树的左子树压入栈中,即不断令treeNode=treeNode.leftChild,然后重复步骤2;

3.不断重复步骤2,直到发现treeNode为空,此时从stack中弹出一个节点记为treeNode,打印node的值,并让treeNode= treeNode.right,然后继续重复步骤2;

当stack为空并且treeNode为空时结束

    /** 左-->根-->右     * 非递归实现中序遍历     * @param root     */    public static void MidOrderStack(TreeNode root){        Stack stack = new Stack();        TreeNode treeNode = root;        while(treeNode!=null || !stack.isEmpty()){            while(treeNode != null){                stack.push(treeNode);                treeNode = treeNode.left;            }            if(!stack.isEmpty()){                treeNode = stack.pop();                System.out.print(treeNode.val+" ");                treeNode = treeNode.right;            }        }    }

4、调试略

整个过程和前序遍历都是差不多的,可以自己试着调试一下,这里就不在啰嗦了。

(三)后序遍历

1、后序遍历概念

后序遍历:先左子树,再右子树,最后根节点;可以说为”左-右-根“。

按照:先左子树,再右子树,最后根节点的顺序如果用图来描述一下的话,是这样子的;

2、图解后序遍历

1、首先我们有一颗如下所示的二叉树,这就是我们的初始值:

a303deea1879b91b55a770c3f7a71b27.png

遍历到顺序和结果:

695cec3de4a9999806631c975105c428.png

动态过程图:

37bf36105e611644304d5ccb23ce792b.gif

3、代码实现后序遍历

(1)递归实现

 /** 左-->右-->根     * 后序遍历     * @param root     */    public static void BackOrder(TreeNode root) {  //先序遍历        if (root==null)            return;        //使用递归每次都先进行遍历左孩子        BackOrder(root.left);        //递归遍历右孩子        BackOrder(root.right);        //根        System.out.print(root.val+" ");    }

(2)非递归实现

/** 左-->右-->根     * 非递归实现后序遍历     * @param root     */    public static void BackOrderStack(TreeNode root){        Stack stack = new Stack();        TreeNode treeNode = root;        TreeNode lastVisit = null;   //标记每次遍历最后一次访问的节点        //节点不为空,结点入栈,并且指向下一个左孩子        while(treeNode!=null || !stack.isEmpty()){            while(treeNode!=null){                stack.push(treeNode);                treeNode = treeNode.left;            }            //栈不为空            if(!stack.isEmpty()){                //出栈                treeNode = stack.pop();                /**                 * 这块就是判断treeNode是否有右孩子,                 * 如果没有,则输出treeNode.val,让lastVisit指向treeNode,并让treeNode为空                 * 如果有右孩子,将当前节点继续入栈,treeNode指向它的右孩子,继续重复循环                 */                if(treeNode.right == null || treeNode.right == lastVisit) {                    System.out.print(treeNode.val + " ");                    lastVisit = treeNode;                    treeNode  = null;                }else{                    stack.push(treeNode);                    treeNode = treeNode.right;                }            }        }    }

4、调试略

整个过程和前序遍历都是差不多的,可以自己试着调试一下,这里就不在啰嗦了。

(四)层次遍历

1.首先申请一个新的队列,记为queue;

2.将根结点root压入queue中;

3.每次从queue中出队,并赋值给root,作为下一次的根,然后打印root.val的值,如果root左孩子4.不为空,则将左孩子入队;如果root的右孩子不为空,则将右孩子入队;

重复步骤3,直到queue为空。

/*** 层次遍历* @param root*/public static void levelOrder(TreeNode root){    LinkedList queue = new LinkedList<>();        queue.add(root);        while(!queue.isEmpty()){        root = queue.pop();        System.out.print(root.val+" ");        if(root.left!=null)            queue.add(root.left);        if(root.right!=null)            queue.add(root.right);    }}

结果:

3 9 20 15 7 

三、总结

遍历二叉树可以使用递归的方式和非递归的方式来遍历。

1670704cf9aa11f825f46477f835197c.png

四种遍历:

(1)先序遍历:先访问根节点,再访问左子树,最后访问右子树;可以说为”根-左-右“。

(2)后序遍历:先左子树,再右子树,最后根节点;可以说为”左-右-根“。

(3)中序遍历:先左子树,再根节点,最后右子树;可以说为”左-根-右“。

(4)层序遍历:每一层从左到右访问每一个节点。

所有代码:

package tree;import java.util.LinkedList;import java.util.Stack;/** * @Auther: truedei * @Date: 2020 /20-6-7 17:39 * @Description: //preorder,midorder,backorder */public class TestTrueDeiTree {    public static void main(String[] args) {        TreeNode t1 = new TreeNode(3);        TreeNode t2 = new TreeNode(9);        TreeNode t3 = new TreeNode(20);        TreeNode t4 = new TreeNode(15);        TreeNode t5 = new TreeNode(7);        t1.left=t2;        t1.right=t3;        t3.left=t4;        t3.right=t5;        System.out.println("递归前序遍历:");        PreOrder(t1);        System.out.println();        System.out.println("递归中序遍历:");        MidOrder(t1);        System.out.println();        System.out.println("递归后序遍历:");        BackOrder(t1);        System.out.println();        System.out.println("非递归前序遍历:");        preOrderStack(t1);        System.out.println();        System.out.println("非递归中序遍历:");        MidOrderStack(t1);        System.out.println();        System.out.println("非递归后序遍历:");        BackOrderStack(t1);        System.out.println();        System.out.println("层次遍历:");        levelOrder(t1);    }    /**根-->左-->右     * 递归前序遍历     * @param root     */    public static void PreOrder(TreeNode root) {        if (root==null)            return;        System.out.print(root.val+" ");        //使用递归进行遍历左孩子        PreOrder(root.left);        //递归遍历右孩子        PreOrder(root.right);    }    /**根-->左-->右     * 非递归前序遍历     *     *     * 1,首先申请一个新的栈,记为stack;     * 2,声明一个结点treeNode,让其指向node结点;     * 3,如果treeNode的不为空,将treeNode的值打印,并将treeNode入栈,然后让treeNode指向treeNode的右结点,     * 4,重复步骤3,直到treenode为空;     * 5,然后出栈,让treeNode指向treeNode的右孩子     * 6,重复步骤3,直到stack为空.     *     * @param root     */    public static  void preOrderStack(TreeNode root){        //利用栈的特性(先进的后出),用于存储节点数据,一层一层的往上冒        Stack stack = new Stack<>();        //相当于临时的变量,记录当前的所在节点        TreeNode treeNode = root;        //两个条件满足一个即可继续执行,退出的条件是当前的节点为null和栈也空了,说明没有数据了        //栈空了说明冒到了最上一层,最上一层也遍历成了,就空了        while (treeNode != null || !stack.isEmpty()){            //迭代访问节点的左孩子,并入栈            //一直遍历最左边的节点            while (treeNode != null){                //先根                System.out.print(treeNode.val+" ");                stack.push(treeNode);                //遍历完根,然后还是遍历最左边的。。如果还有的话,还是遍历最左边的                treeNode  = treeNode.left;            }            //如果节点没有左孩子,则弹出栈顶节点,访问节点右孩子            if(!stack.isEmpty()){                treeNode = stack.pop();                treeNode = treeNode.right;            }        }    }    /** 左-->根-->右     * 中序遍历     * @param root     */    public static void MidOrder(TreeNode root) {  //先序遍历        if (root==null)            return;        //使用递归每次都先进行遍历左孩子        MidOrder(root.left);        System.out.print(root.val+" ");        //递归遍历右孩子        MidOrder(root.right);    }    /** 左-->根-->右     * 中序遍历     *     *1. 申请一个新栈,记为stack,申请一个变量treeNode,初始时令treeNode为头节点;     *2. 先把treeNode节点压入栈中,对以treeNode节点为头的整棵子树来说,依次把整棵树的左子树压入栈中,即不断令treeNode=treeNode.leftChild,然后重复步骤2;     *3. 不断重复步骤2,直到发现treeNode为空,此时从stack中弹出一个节点记为treeNode,打印node的值,并让treeNode= treeNode.right,然后继续重复步骤2;     *4. 当stack为空并且treeNode为空时结束。     * @param root     */    public static void MidOrderStack(TreeNode root){        Stack stack = new Stack();        TreeNode treeNode = root;        while(treeNode!=null || !stack.isEmpty()){            while(treeNode != null){                stack.push(treeNode);                treeNode = treeNode.left;            }            if(!stack.isEmpty()){                treeNode = stack.pop();                System.out.print(treeNode.val+" ");                treeNode = treeNode.right;            }        }    }    /** 左-->右-->根     * 后序遍历     * @param root     */    public static void BackOrder(TreeNode root) {  //先序遍历        if (root==null)            return;        //使用递归每次都先进行遍历左孩子        BackOrder(root.left);        //递归遍历右孩子        BackOrder(root.right);        //根        System.out.print(root.val+" ");    }    /** 左-->右-->根     * 非递归实现后序遍历     * @param root     */    public static void BackOrderStack(TreeNode root){        Stack stack = new Stack();        TreeNode treeNode = root;        TreeNode lastVisit = null;   //标记每次遍历最后一次访问的节点        //节点不为空,结点入栈,并且指向下一个左孩子        while(treeNode!=null || !stack.isEmpty()){            while(treeNode!=null){                stack.push(treeNode);                treeNode = treeNode.left;            }            //栈不为空            if(!stack.isEmpty()){                //出栈                treeNode = stack.pop();                /**                 * 这块就是判断treeNode是否有右孩子,                 * 如果没有,则输出treeNode.val,让lastVisit指向treeNode,并让treeNode为空                 * 如果有右孩子,将当前节点继续入栈,treeNode指向它的右孩子,继续重复循环                 */                if(treeNode.right == null || treeNode.right == lastVisit) {                    System.out.print(treeNode.val + " ");                    lastVisit = treeNode;                    treeNode  = null;                }else{                    stack.push(treeNode);                    treeNode = treeNode.right;                }            }        }    }    /**     * 层次遍历     * @param root     */    public static void levelOrder(TreeNode root){        LinkedList queue = new LinkedList<>();        queue.add(root);        while(!queue.isEmpty()){            root = queue.pop();            System.out.print(root.val+" ");            if(root.left!=null)                queue.add(root.left);            if(root.right!=null)                queue.add(root.right);        }    }}class TreeNode {    int val;//每个节点存放的数据    TreeNode left;//左节点    TreeNode right;//右节点    TreeNode(int x) { val = x; }}

作者:TrueDei

原文链接:https://blog.csdn.net/qq_17623363/article/details/106612418

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值