【java数据结构】模拟二叉树的链式结构之孩子表示法,掌握背后的实现逻辑

📢编程环境:idea
📢树结构,以及叶子,结点,度等一些名词是什么意思,本篇不再赘述。

1. 认识二叉树

二叉树是重要的数据结构之一,之所以叫二叉树,是因为用它组织的数据逻辑上就像一颗自然界中倒挂的二叉树一样。即树的根朝上,所有树枝只要遇到分叉,当下最多分两个叉,树枝朝下。
在这里插入图片描述

1.1 二叉树的结构

1.11逻辑结构

先来理解二叉树的逻辑结构

二叉树是一种逻辑上非线性的数据结构。一颗二叉树是一个有限个元素的集合。该集合或者为空,或者由一个根元素加上两颗别称为左子树和右子树的二叉树组成。这也是二叉树的递归性,一颗二叉树是由一个套一个的子二叉树构成的。还要注意的是,左右子树的次序不能颠倒。

在这里插入图片描述

在这里插入图片描述

对于二叉树中的每个元素,它都有唯一一个前驱(除头元素没有前驱)和不超过两个后继(不超过两个后继的意思是:后继的个数可以是0,1,2)
在这里插入图片描述
对于任意的二叉树都是由以下几种情况复合而成的:在这里插入图片描述


1.12存储结构

再来理解二叉树的(物理)存储结构

二叉树的存储结构分为顺序存储和类似于链表的链式存储。本篇以实现二叉树的链式存储结构为主。

先来回忆一下链表的链式存储:用链表存储一组元素,其中每个元素都存储在一个结点中。单链表的每个结点中存储元素的同时,还要存储下一个元素的地址,双向链表的每个结点中存储元素的同时,还要存储上一个元素的结点地址和下一个元素的结点地址。

在这里插入图片描述

而二叉树的链式存储,也是把每个元素都存储在一个结点中,但是不同于链表为了保持逻辑上的线性结构,结点中存元素的同时还要存储前一个和后一个元素的节点地址。而二叉树在逻辑上是非线性的树结构,为了保持该结构,二叉树的每个节点中存储元素的同时,还要存储它的前驱结点地址和后继结点地址。

二叉树的每个结点中如果同时存元素,前驱结点地址,后继结点地址,统一又把这种链式存储方式叫做孩子表示法。例如用孩子表示法存储下面这颗二叉树:
在这里插入图片描述

二叉树的每个结点中如果存元素,和后继结点地址,统一又把这种链式存储方式叫做孩子双亲表示法。
在这里插入图片描述其中,自上而下看一颗二叉树,根元素所在的结点又叫做根结点。
在这里插入图片描述

本篇要用java语言模拟实现孩子表示法存储二叉树,以及一些常用基本操作孩子双亲表示法存储二叉树会在后续总结AVL树,红黑树的博客中重点介绍,二叉树的顺序存储会在后续总结 的博客中重点介绍。

1.2 两种特殊的二叉树

1.21 满二叉树

一颗二叉树,如果每层的结点数都达到最大值,即每个元素都有两个后继,则这颗二叉树就是满二叉树。

在这里插入图片描述

其中,假设满二叉树的层数是k,则该满二叉树的节点总数是(2^k)-1即2的k次方-1

在这里插入图片描述

1.22 完全二叉树

完全二叉树是由满二叉树引出来的。

对于深度为k的,有n个结点的二叉树,当且仅当它的每个结点都与深度为k的满二叉树中编号从0至n-1的节点一一对应时,该二叉树才能称之为完全二叉树。(其中满二叉树中结点按从左到右,从上到下的顺序进行编号,此处假设根节点从0号开始)。

在这里插入图片描述

满二叉树是特殊的完全二叉树。

1.3 二叉树的性质

1.31. 若规定只有根节点的二叉树深度为1,则深度为k的二叉树最多有2k-1个结点(满二叉树)。(k>=0)

在这里插入图片描述

1.32. 若规定根节点是第一层,则一颗非空二叉树的第 i 层上最多有 2(i-1) 个结点。(i>0)

在这里插入图片描述

1.33. 对任何一颗二叉树,如果其叶结点个数为n0,度为2的结点个数为n2,则有n0=n2+1

  • 其实就是,在任意一颗二叉树中,叶子结点个数永远比度为2的结点个数多一个
    在这里插入图片描述

    • 在二叉树中,若一个结点没有后继,则该结点就是叶子结点
    • 就是一个结点有几个后继,若一个结点有一个后继,该结点度为1;若一个结点有两个后继,该结点度为2。
  • 推论:

    • 在任意一颗二叉树当中,假设,该二叉树的叶子结点个数为n0,度为1的结点个数为n1,度为2的结点个数为n2,设这颗二叉树的总结点个数为N。所以:N=n0+n1+n2
    • 又因为:一颗有N个结点的树有N-1条边。叶子结点向下不会产生边;度为1的结点向下会产生一条边,即n1个结点产生边的个数是n1。度为2的结点向下会产生两条边,即n2个结点产生边的个数是2*n2。所以:N-1=n1+2 * n2,即N=n1+2*n2 +1
    • 两个式子联立最后推出:n0=n2+1

1.34. 具有n个结点的完全二叉树,其深度k为log2(n+1) 向上取整。

在这里插入图片描述

1.35. 对于有n个结点的完全二叉树,假设按照从左到右,从上到下的顺序对所有结点从0开始编号,根节点为0号,最后一个结点时 n-1 号。

在这里插入图片描述
- 已知 一个结点是 i 号,则它的双亲节点(前驱结点)的编号是 (i-1)/2 。根结点是0号,没有双亲结点。
- 已知 一个结点是 i 号,则它的左孩子结点的编号是 2i+1(2i+1<n)。若2i+1>n,该左孩子不存在,即 i 号结点没有左孩子。
- 已知 一个结点是 i 号,则它的右孩子结点的编号是 2i+2(2i+2<n)。若2i+2>n,该右孩子不存在,即 i 号结点没有右孩子。

在这里插入图片描述

1.36. 二叉树的递归性

先回忆一下递归的相关内容:
递归的两个必要条件:

  • 原问题可以被不断划分为有限个子问题,且子问题和原问题的解法相同。原问题和子问题的关系就像是套娃那样,且原问题和子问题有相同的解法。
    • 二叉树是由一个套一个的子二叉树构成的,子二叉树又是由一个套一 个的子二叉树构成的。即对于二叉树中的每个结点,该结点都是当下所在子树的根节点
      在这里插入图片描述
  • 递归必须要有出口,即子问题不能再被划分为子问题时,即递归出口。
    • 二叉树的最小构成单位是一颗空树,对于一颗非空二叉树来说,叶子结点的左右孩子就是该二叉树中的最小子二叉树。
      在这里插入图片描述

所以想要解决递归的相关问题,必须要清楚最小单位子问题子问题的解法。空树就是最小单位的一颗二叉树。

递归的整体解题思路:

  • 先确认子问题的解法和递归出口。
  • 先不断递,一直递到递归出口,再开始不断归,不断递归的过程也是依次解决所有子问题,最终解决原问题的过程。


二叉树的结构和性质我们都要非常熟悉,才能在此基础上更好的解决有关二叉树的相关题目,以及在实际场景中应用二叉树。

2. java语言,模拟实现二叉树的链式结构之孩子表示法(以存char类型元素为例)

用java语言模拟实现一个二叉树,java是面向对象语言,所以首先要创建一个类来表示二叉树,这个类里面有一个静态内部类,一个成员变量,若干个成员方法。

  • 静态内部类是为了描述结点,结点中要存储元素,左孩子地址,右孩子地址(孩子表示法);
  • 成员变量是二叉树的根结点;
  • 通过成员方法对二叉树进行遍历,以及增删改查操作。
public class MyBinaryTree {
    public static class Node{
        int val;//元素域
        Node left;//左孩子的引用,常常代表左孩子为根的整颗左子树
        Node right;//右孩子的引用,常常代表右孩子为根的整颗右子树
        Node(int val){
            this.val = val;
        }
        private Node root;//指向二叉树的根节点
        
        //仅博客需要,先穷举法创建一个二叉树,方便演示二叉树的遍历,实际场景中不要用这种方法创建二叉树
        public void createMyBinaryTree(){

        }
        //前序遍历
        void preOrder(Node root){
            ;
        }
        //中序遍历
        void inOrder(Node root){
            ;
        }
        //后序遍历
        void postOrder(Node root){
            ;
        }
        //二叉树的构建(结点中存char类型的元素)
        Node create(String str){
            return null;
        }

        // 获取树中节点的个数
        int size(Node root){
            return -1;
        }

        // 获取叶子节点的个数
        int getLeafNodeCount(Node root){
            return -1;
        }

        // 子问题思路-求叶子结点个数
        int getLeafNodeCount1(Node root){
            return -1;
        }

        // 获取第K层节点的个数
        int getKLevelNodeCount(Node root,int k){
            return -1;
        }

        // 获取二叉树的高度
        int getHeight(Node root){
            return -1;
        }

        // 检测值为value的元素是否存在
        Nodend(Node root, int val){
            return null;
        }

        //层序遍历
        void levelOrder(Node root){

        }

        // 判断一棵树是不是完全二叉树
        boolean isCompleteTree(Node root){
            return true;
        }
    }
}

2.1 递归实现二叉树的遍历

遍历是什么?

沿着某条搜索路线,对数据结构中的每个元素,访问一次且仅访问一次。
要注意的是:

  1. 访问结点所做的操作依赖于具体的应用问题(如:打印节点内容、节点内容加1)。
  2. 在搜索路径中不会重复搜索元素。

例如遍历线性表:

如果是要遍历顺序存储的线性表,即让数组下标从0依次向后走,同时对数组中的所有元素做一次且仅做一次访问。

在这里插入图片描述

如果是要遍历链式存储的线性表,只需要新建一个引用变量cur,让cur指向线性表的头结点,然后让cur顺着结点中存储的下一个元素地址依次向后走,同时依次对线性表中的所有元素做一次且仅做一次访问。

在这里插入图片描述
对于一对一的线性结构来说,线性表中的元素有唯一前驱和唯一后继(首元素无前驱,尾元素无后继),所以遍历线性表非常简单,即沿着搜索路线,搜到一个结点,就访问结点中的元素。而且用代码实现线性表遍历也非常简单,遍历数组时,借助数组下标;遍历单链表时,借助结点next域。

在本篇模拟实现的是,孩子表示法链式存储二叉树,遍历该二叉树其实和遍历线性表一样的,也是沿着搜索路线,搜到一个结点,就访问结点中的元素。难点在于

  1. 如何用代码实现呢?借助二叉树的递归性完成遍历,从根节点开始递,递到递归出口,再归。
    在这里插入图片描述
  2. 二叉树是一对二的非线性结构,二叉树中的每个元素虽然有唯一前驱但是最多可以有两个后继(除根元素没有前驱),在搜索完一个结点后,会面临左右孩子两个选择,先选哪个其实最终都可以完成遍历,但是为了避免在共同协作的场景中多人得到不同的遍历结果产生混乱,根据遍历根节点的先后次序做了统一约定。基于此,有三种遍历二叉树的方式,分别是前序遍历,中序遍历,后序遍历。

根据递归思路,根据搜索路径遍历二叉树中的所有结点:
4. 如何不重复的遍历子二叉树中所有结点?按照根结点,左孩子,右孩子的搜索路径。
5. 找到最小子二叉树?空树就是最小子二叉树。
6. 从根节点开始递,递到递归出口,再归。且对于每棵子二叉树,都是按照根结点,左孩子,右孩子的搜索路径遍历。


2.11 前序遍历

先序遍历一颗子叉树:访问根节点,遍历左子树,遍历右子树。
递归出口:空树。
先序遍历一颗二叉树:利用递归,从根节点进入,先序遍历每一颗子二叉树。
在这里插入图片描述

2.12 中序遍历

中序遍历一颗子叉树:遍历左子树,访问根结点,遍历右子树。
递归出口:空树。
先序遍历一颗二叉树:利用递归,从根节点进入,中序遍历每一颗子二叉树。

在这里插入图片描述

2.13 后序遍历

后序遍历一颗子叉树:遍历左子树,遍历右子树,访问根结点,。
递归出口:空树。
后序遍历一颗二叉树:利用递归,从根节点进入,后序遍历每一颗子二叉树。
在这里插入图片描述

除前中后序遍历,还可以通过层序遍历依次访问二叉树中的每个结点,但在实现层次上,层序遍历不依靠二叉树的递归性和链式存储,而是要借助 数据结构栈 完成整个遍历过程。

对于所有的数据结构来说,遍历该数据结构几乎是其任何操作的基础。所以搞懂数据结构的遍历至关重要!

2.2 创建一颗二叉树

  • 44
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值