1、二叉树的学习,前中后序遍历的实现

概念部分

关于数的常用数据

  1. 节点,二叉树当中的每个点可称为一个节点

  2. 根节点,最顶部的节点就是根节点,一棵树,有且唯一有一个最顶部的根节点

  3. 父节点,该节点下如果还有节点的话,那么他自身就是一个父节点

  4. 子节点,同理,如果该节点的上级还有节点的话,那么他自身也是一个子节点

  5. :节点的值即为节点的

  6. 叶子节点,没有子节点的节点就是叶子节点

  7. 路径:从根节点到该节点的路线就叫做路径

  8. 子树:这个概念比较模糊,类似于父节点下有子节点,并且该父节点的上方还有节点?

  9. 层:位于同级目录下的节点他就是一层

  10. 树的高度:最大层数即为树的高度

  11. 森林:多颗子树形成的森林--根节点下的多颗子树

对于比较纠结的地方概念部分不用死钻,重点还是能够写出树这种结构,用树解决问题

1、二叉树

1,.1、二叉树的概念

  1. 树分为很多种,每个节点最多只能有两个子节点的树称之为二叉树

  2. 二叉树的子节点至多不能超过两个,所以它的子节点又被称为左节点右节点

    • 虽然后面两个二叉树只有一个子节点,但仍可以称作为二叉树,因为子节点至多存在两个嘛

  3. 满二叉树:如果当前的二叉树,的,所有的,叶子节点都在最后一层,并且节点数的总数为2^n - 1,(2的n方减一)

  4. 完全二叉树:如果该二叉树的所有叶子节点都在最后一层,或者是倒数第二层

    • 且,最后一层的叶子节点左边连续

    • 倒数第二层的叶子节点右边连续

    • 那么该树就可以称之为完全二叉树

  5. 什么是最后一层叶子节点左边连续?

  6. 什么是倒数第二层的叶子节点右边连续?

 

1.2、二叉树的前中后序代码实现

1、节点类的设计

设计概念

  • 二叉树是根据节点构成的,那么我们首先需要一个节点类Node对吧

  • 其次就是对咱们Node节点类的设计

  • 属性的设计

    • 首先咱们需要知道每个节点的索引或者唯一性指标吧?这里我们就选择id作为唯一性指标

    • 其次,既然是数据结构,数据结构的话内部需要存东西吧?那我们定义一个属性,叫做name吧(任意属性,T)

    • 最重要的一点来了,二叉树,由节点构成,并且每个节点下的子节点至多不超过两个?什么意思?我们的节点需要子节点吗?需要当然需要子节点必须要有,并且左右都要有,你可以不对其进行值的赋予,但是他作为一个东西,必须有自身存在的意义,这是我的理解

    • 那么我们就需要对其进行左右子节点的设计了

      • 一个左节点,一个右节点,叫子树的话我觉得也可以吧?

  • 代码设计

  • 构造方法的设计

    • 既然有这么一个节点Node,那当我在创建这个节点的时候,需要传递什么参数进去呢?

    • id作为一个节点的唯一性标识,这是我们需要的

    • 而name的话作为该节点当中存储的数据也是我们需要的

    • 左子节点和右子节点呢?需要吗?

      • 不需要,因为一个节点本身确实是包含左右子节点还有其他属性构成的,但是,这个属性(子节点)只需要存在即可,知道有这么个东西,具体是否存在,看操作者的意愿,你想让他有子节点,那么他就有子节点,想让他没有,那他就没有,所以当我们进行有参构造的时候,不需要进行子节点的设计但是会通过get和set方法去对子节点进行一个获取和赋值

  • 代码设计

  • 我们现在作为测试节点类的话,那肯定需要一个判断标识嘛,这个判断标识呢,我们就通过控制台打印的方式打印该节点的toString方法,当然,我们需要对其进行重写覆盖,不然打印出来的就是一串地址咯

  • 同理,toString打印的时候需要打印什么属性?

    • id和name是必须的对吧?

    • 左右子节点呢?不用吧?如果我这颗二叉树结构很复杂,那么我打印一个节点那不是要把屏幕撑爆?内容会相当的冗杂,所以我们这里就打印唯一性标识id节点当中存储的数据内容name即可,自己该干嘛干嘛,别老想着一只公鸡要下蛋,不是你的活你非要干~

  • 代码实现

2、节点类的整体代码实现

/**
 * 这是我们的节点类
 */
@Data
public class Node {
    /**
     * 它作为一个节点需要满足什么条件?
     * 首先自身的属性得有吧?例如id or name?
     * 其次,需要子节点吧?子节点无论是否为空我们都需要摆在这里对吧?
     * 我这里是使用的Maven框架当中的LomBok依赖哦~所以我不需要写Get和Set方法
     */
    private Integer NodeId; // 节点ID
    private String NodeName; // 节点名称
    private Node leftNode; // 左子树
    private Node rightNode; // 右子树
​
    /**
     * 变量定义完毕,那么我们需要给这个Node对象设置构造方法吧?
     * 实例化的时候需要干什么?
     * 先来进行一个判断,我实例化该对象的时需要给他传递节点对象吗?
     * 不需要吧?那么有参构造的时候无需传递左子树和右子树吧?
     * 不然等下怎么递归?
     */
​
    // 有参构造-- 无需传递子树对象
    public Node(Integer nodeId, String nodeName) {
        NodeId = nodeId;
        NodeName = nodeName;
    }
​
    /**
     * 我定义的变量都是私有变量,那么我都需要给他们设置get和set吧?
     * 有点麻烦了,那我引入一个Lombok简化我的开发吧,至少简化我get和set的开发吧?
     * 没有使用Lombok的话就老老实实把get和set方法写上去哦
     */
​
    /**
     * 下一步,设置下toString吧,因为引入了Data注解,那么我们需要
     * 对它定义好的toString方法进行覆盖
     */
    @Override
    public String toString() {
        return "Node{" +
                "NodeId=" + NodeId +
                ", NodeName=" + NodeName +
                '}';
    }
}

 

3、二叉树类的设计

设计概念

  • 既然了解到了节点的创建和组成,那么二叉树的创建和设计就显得比较简单了

  • 二叉树需要什么?根节点嘛~

  • 根节点从哪儿来?构造方法或者set传参嘛~

代码实现

4、二叉树类的整体代码实现

/**
 * 这是一个二叉树类
 */
public class BinaryTree {
    // 他只需要一个根节点即可
    private Node root;
​
    // 只需要给我这个根节点我就可以通过这个根节点对二叉树进行遍历
    public void setRoot(Node root) {
        this.root = root;
    }
​
    // 前序遍历
    public void preOrder(){
        // 通过判断根节点来判断 该二叉树是否是一个空树
        if(this.root !=null){
            // 不为空那么就可以开始前序遍历了
            // 前序遍历是从根节点开始的,那我们直接调用根节点的perOrder的方法即可
            this.root.preOrder();
        }
    }
​
    // 中序遍历呢
    public void infixOrder(){
        // 左中右
        // 根节点不为空
        if(this.root!=null){
            // 开始中序递归遍历
            // this.root.getLeftNode().infixOrder();
            /**
             * 我算是明白了为什么,老师会说遍历遍历都是从父节点开始的
             * 也就是根节点root,我这里犯的错误就是一个非常典型的例子
             * 在我的例子当中,root的左子树下是没有子节点了
             * 这个时候我从左子树开始,那么就会进行中序递归遍历
             * 直接打印这个左子树的构造方法
             * 然后因为该子树下没有子节点,所以下面哪条右子树的判断也不存在
             * 所以最终最终,就只会打印左子树这一条记录
             * 因为这个时候的根节点,是左子树,而非root
             * 很妙啊
             */
            this.root.infixOrder();
        }
    }
​
    // 后序遍历
    public void postOrder(){
        // 根节点不为空
        if(this.root!=null){
            // 开始后序递归遍历
            this.root.postOrder();
        }
    }
​
​
}

 

5、二叉树的前中后序遍历思路

我要着重说明一下,无论是前中后序遍历,起点永远都是从root根节点开始的,永远都是!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

概念

  • 二叉树的遍历方式分为三种,前、中、后顺序遍历

    1. 前序遍历:顾名思义,先输出父节点(根节点)的内容,再遍历左子树右子树

    2. 中序遍历遍历左子树的内容,遍历父节点的内容,最后遍历右节点的内容

    3. 后序遍历:先遍历左右子树的内容,最后遍历父节点的内容

  • 通过查看输出父节点内容的顺序,那么我们就可以确定该二叉树的遍历方式是前中后序的哪一种

代码实现思路

前序遍历--代码实现

莫得关系,这张图看起来生硬的话那我们来代码设计一下看看吧~

首先需要给咱们的节点类设置前序遍历的方法对吧,preOrder

/**
     * Node节点类的前序遍历方法
     * 1、前序遍历首先肯定是从根节点开始的
     * 2、判断当前左子树是否为空,不为空那么我们就对其进行前序遍历的递归
     * 3、判断右子树是否为空,不为空那么我们就对其进行前序递归遍历
     */
    public void preOrder(){
        // 前序遍历,那我们要先打印根节点吧?
        System.out.println(this); // 谁调用,this的指向就是谁
        // 判断左子树是否存在
        if(this.leftNode != null){
            // 递归继续遍历
            this.leftNode.preOrder();
        }
        // 判断右子树是否存在
        if(this.rightNode != null){
            // 递归继续前序遍历
            this.rightNode.preOrder();
        }
    }

那么二叉树需要这个方法吗?当然需要,方法肯定从二叉树哪里开始调用的啊

// 二叉树的前序遍历
    public void preOrder(){
        // 通过判断根节点来判断 该二叉树是否是一个空树
        if(this.root !=null){
            // 不为空那么就可以开始前序遍历了
            // 前序遍历是从根节点开始的,那我们直接调用根节点的perOrder的方法即可
            this.root.preOrder();
        }
    }

前序遍历的运行过程

运行过程的文字解析,适合给没明白的小宝贝们理解使用

  1. root对象是Node对象,他的preOrder方法就是Node的preOrder

  2. 首先是不是要打印this,this是谁现在?root嘛=>1

  3. 判断左子树是否存在?存在吗?当然存在!是谁? id=2嘛

    • id=2的节点开始调用preOrder

    • 是不是要开始打印this了? this是谁? id=2的节点Node=>2

    • id = 2的节点还有左子树吗?没有,还有右子树吗?没有

  4. 那么当前root下判断左子树的语句是不是运行完毕了?

  5. 接下来是不是要判断右子树了?右子树存在吗?存在

    • id=3的Node对象开始调用preOrder方法,进来是不是要先打FDthis? this是谁? id=3的Node嘛=>3

    • 该节点下的左子树存在吗?不有在,右子树存在吗?存在

    • id=3的Node下的右子树开始调用preOrder

      • 进来是不是也要打印this? this是谁? id=4的Node对象嘛=>4

      • 该节点下还有左子树吗?没有,还有右子树吗?也没有

  6. root下的右子树语句判断完毕,还有下调语句吗?没有

中序遍历--代码实现

一样的,来设计方法,就相当于改下顺序,中序遍历的过程是什么样子的?

中序遍历的运行过程

/**
     * 中序遍历方法
     * 1、先从左子树开始中序遍历,
     * 2、输出父节点
     * 3、最后从右子树中序遍历结束
      */
    public void infixOrder(){
        // 如果左子树不为空
        if(this.leftNode !=null){
            // 递归遍历中序遍历
            this.leftNode.infixOrder();
        }
        // 输出父节点
        System.out.println(this);
        // 如果右子树不为空
        if(this.rightNode != null){
            // 递归遍历中序遍历
            this.rightNode.infixOrder();
        }
    }
// 中序遍历呢
    public void infixOrder(){
        // 左中右
        // 根节点不为空
        if(this.root!=null){
            // 开始中序递归遍历
            // this.root.getLeftNode().infixOrder();
            /**
             * 我算是明白了为什么,老师会说遍历遍历都是从父节点开始的
             * 也就是根节点root,我这里犯的错误就是一个非常典型的例子
             * 在我的例子当中,root的左子树下是没有子节点了
             * 这个时候我从左子树开始,那么就会进行中序递归遍历
             * 直接打印这个左子树的构造方法
             * 然后因为该子树下没有子节点,所以下面哪条右子树的判断也不存在
             * 所以最终最终,就只会打印左子树这一条记录
             * 因为这个时候的根节点,是左子树,而非root
             * 很妙啊
             */
            this.root.infixOrder();
        }
    }

文字描述部分

  1. root节点开始调用infixOrder

  2. root的左子树为空吗?不为空,那么左子树id=2开始调用infixOrder

    • id=2的节点进入infixOrder中,他的左子树为空吗?为空

    • 开始打印id=2这条节点的数据=>2

    • id=2的节点右子树为空吗?为空! OK

  3. 至此,root当下的左子树递归中序遍历完毕

  4. 开始打印当前的节点,this是谁? id=1嘛~=>1

  5. root下的右子树为空吗?不为空,那么id=3的节点开始调用infixOrder

    • id=3的左子树为空吗?为空,左子树的判断完毕

    • 开始打Ethis,this是谁? id=3的节点嘛~=>3

    • id=3的右子树为空吗?不为空

    • id=4的节点开始调用infixOrder方法

      • id=4的节点左子树为空吗?为空

      • this是谁? id=4嘛=>4

      • id-4的右子树为空吗?为空

  6. 至此,root下的全部语句运行完毕

后续遍历--代码实现

还是一样的味道~,不过后续遍历要注意哦,跟之前的感觉有点不一样

图文描述

Node下的postOrder

// 后序遍历
    public void postOrder(){
        // 如果左子树不为空
        if(this.leftNode !=null){
            // 递归遍历中序遍历
            this.leftNode.postOrder();
        }
        // 如果右子树不为空
        if(this.rightNode != null){
            // 递归遍历中序遍历
            this.rightNode.postOrder();
        }
        // 最终输出父节点
        System.out.println(this);
    }

二叉树下的postOrder

// 后序遍历
    public void postOrder(){
        // 根节点不为空
        if(this.root!=null){
            // 开始后序递归遍历
            this.root.postOrder();
        }
    }

文字描述

  1. root进入postOrder方法当中

  2. root下的左子树为空吗?不为空,id=2的Node开始调用postOrder

    • id=2的节点左子树为空吗?为空,右子树为空吗?为空

    • 开始打印id=2下的this,this是谁? id=2嘛=>2

  3. root下的左子树判断语句结束,开始进行右子树为空判断

  4. root下的右子树为空吗?不为空,id=3的Node调用postOrder

    • id=3的节点左子节点为空吗?为空

    • id=3的节点右子节点为空吗?不为空,id=4进入postOrder中

      • id=4的节点左子树为空吗?为空,右子树为空吗?为空

      • 开始打印this,this是谁? id=-4嘛~=>4

    • 至此id=3下的右子树判断结束,开始打印this,this是谁? id=3嘛~=>3

  5. 至此,root下的左右子树判晰完毕,开始打印root下的this,this是谁?id=1嘛~=>1

1.3、设计方法开始测试

假如这里有一颗二叉树,他的结构是这样的

  • 那么我们首先应该怎么做?

  • 先创建一颗二叉树对吧?

  • 然后开始创建我们的节点,因为现在是起步节点,先不忙使用递归的方式创建二叉树

  • 那么节点创建完毕,我们是不是要开始编织节点的关系?

  • 编织完关系过后,我们是不是需要给二叉树的对象设置根节点--root?

  • 开始调用前中后序遍历

接下来的测试方法我使用的是Maven的Junit的测试类

1、前置方法的设置--@Before

/**
     * 前置方法
     * @param=>@Before:可以理解为每个@Test修饰的方法在执行之前都会先调用@Before下方法
     * @param=>@Test:该注解修饰下的方法为待测试的方法,避免重复写很多个main方法
     */
    @Before
    @Test
    public void test1(){
        // 创建一颗二叉树,实例化
        binaryTree = new BinaryTree();
        // 创建几个节点
        Node root = new Node(1, "张三");
        Node node1 = new Node(2, "李四");
        Node node2 = new Node(3, "王五");
        Node node3 = new Node(4, "申六");
        // 对根节点进行子树分配,分配关系
        root.setLeftNode(node1);// 左子树为node1
        // 右子树自身也有一个子节点--node3
        node2.setRightNode(node3); // 右子树为node3
        // 最终再为根节点将复制完毕的右子树进行分配
        root.setRightNode(node2);// 右子树为node2
        // 将分配好的根节点赋值给二叉树
        binaryTree.setRoot(root);
    }

2、前序遍历

来吧,开始调用前序遍历

/**
     * 前序遍历
     */
    @Test
    public void preOrder(){
        // 开始前序遍历,结果肯定是1234
        binaryTree.preOrder();
    }

结果为

3、中序遍历

/**
     * 中序遍历
     */
    @Test
    public void infixOrder(){
        System.out.println("中序遍历开始");
        // 中序遍历--2134
        binaryTree.infixOrder();
    }

结果为

4、后序遍历

代码设计

posted @ 2022-05-20 21:25  澜璨  阅读(90)  评论(0)    收藏  举报