二叉树(一)树结构基础,先序、中序、后序遍历代码,规律详细解析,看完包懂!

前言❤️❤️

hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹
这篇博客会进入到树结构的学习,会解析什么是树结构,什么是二叉树,以及二叉树的几种遍历方式,和规律的分析
这部分内容不仅是面试的高频考点,也是408的常考内容,这篇博客会从最基础的东西开始解析,包能看懂的🐵🐵🐵
这个专栏的数据结构是代码都是用Java来写的,JavaSE专栏现在已经全部更新完成,铁汁们复习基础知识时非常推荐使用,可以试一下💪💪💪
在这里插入图片描述
🎇个人主页:洋不写bug的博客
🎇所属专栏:数据结构专栏
🎇复习Java基础知识:Java学习之旅,从入门到进阶
🎇铁汁们对于数据结构基础的各种核心知识(不太常用的也有😆),都可以在上面的数据结构专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~

1,树结构简介

线性结构,就是排成一条线,任何一个元素,都只有一个前驱和一个后继,像链表、顺序表、栈、队列这些都属于线性结构

树这样的结构,就是非线性结构,最下面是一个树根,树根上有很多树枝,每个树枝又会分叉,一个节点的后继可能有多个节点

在这里插入图片描述



像家谱就是一个经典的数据结构,某个节点可以向下延伸出很多节点,延伸出的节点这个节点的“子节点”,这个节点就称为“父节点”

如下图,“爷爷&奶奶”这个节点,就有三个子节点(爸爸&妈妈、二叔&二婶、三叔&三婶),“爷爷&奶奶”就是这三个节点(爸爸&妈妈、二叔&二婶、三叔&三婶)的父节点

在这里插入图片描述

在树结构中,一个父节点,可以包含多个子节点,但是一个子节点,只能有一个父节点(就是一个父亲能有多个儿子,但一个儿子只能有一个父亲),最上面的那个节点就是根节点,上面家谱中,"曾祖父&曾祖母"这个节点就是根节点



在日常生活中,很多结构都是树结构,例如计算机的存储,计算机有三个节点,C盘、D盘、E盘,每个盘下对应着很多目录(文件夹)和文件

在这里插入图片描述


再比如学校,也是树结构

在这里插入图片描述



接下来铁汁们判断下,下图中的三个结构是不是树结构,是不是树
判断是不是子树,有三个标准,都满足才是树结构

在这里插入图片描述

结构1中,C和D都是A的子节点(子树),C和D相交了;结构2中,E有两个父节点,D和B;结构3中,G有两个父节点,A和D;因此,这三个结构都不是树结构
只要第一个和第二个标准有一个不满足,那第三个标准就一定不满足



树结构还要很多概念,接下来就以下图中的树结构为例,来介绍下这些概念(树结构的元素,专业术语应该是结点,而不是节点,因此下面解析树结构都会用结点)

在这里插入图片描述

  • 结点的度:一个结点含有子树的个数称为结点的度,子树可以理解为子结点,子结点可以视为是另一棵树的根结点,A的结点的度就是6
  • 树的度:一棵树中,所有结点度的最大值就是树的度,中A结点的度为6,是最大的,所以树的度就是6
  • 叶子结点:度为0的结点就是叶子结点,B、C、H、I、P、Q、K、L、M、N都是叶子结点
  • 父结点(双亲结点):这个很好理解,例如A是B的父结点,J是P的父结点
  • 子结点(孩子结点):B是A的子结点,P是J的子结点
  • 根结点:一棵树中,没有父结点的结点就是根结点,A就是根结点
  • 结点的层次:从根节点开始,根节点所在的层为第一层,下一层为第二层
  • 树的深度或高度:结点的最大层次,图中树的深度为4
  • 兄弟结点:兄弟结点有相同的父结点,B和C互为兄弟结点,K和L和M也互为兄弟结点
  • 森林: m (m ≥ 0) 棵互不相交的树的集合(没有树就是空森林)




树结构有三种表示方式,分别是双亲表示法、孩子表示法、孩子双亲表示法

双亲表示法就是拿到一个结点A,可以通过A.parent找到这个结点的父结点,几乎不会用到

class Node {
    String val;
    Node parent;
}



孩子表示法就是拿到一个结点A,就可以通过A.children找到A的所有子结点,孩子表示法是日常使用最多的,拿到根结点,就能访问到树中的所有结点

class Node {
    String val;
    List<Node> children;
}



孩子双亲表示法,拿到一个结点A,既可以通过A.parent拿到A结点的父结点,也可以通过A.children拿到A结点的子结点,但这种方法空间利用率就比较低

class Node {
    String val;
    List<Node> children;
    Node parent;
}



孩子兄弟表示法,拿到一个结点A,可以通过A.firstChild拿到A的第一个子结点B,接着可以通过B.brotherNodes拿到B的所有兄弟结点,这个就更加小众了,只有在特定场景下会用到,了解下即可

class Node {
    String val;
    Node firstChild;
    List<Node> brotherNodes;
}

2,二叉树简介

二叉树:树上任意结点的度,都不超过2,那这棵树就是二叉树,像只有一个结点,或者结点的度为1,也都属于二叉树,在数据结构中,主要学习的树结构就是二叉树



满二叉树:整棵树,没有只存在一个分支的情况,如下图
满二叉树第k层有2 ^ (k - 1)个结点
假设一共有n层,那满二叉树就有2 ^ n - 1个结点

在这里插入图片描述

完全二叉树,相当于在满二叉树的基础上,缺失了右下角

在这里插入图片描述



可以这样理解,完全二叉树在每一层,都是从左往右铺结点的,铺完铺不完无所谓,重要的是中间不能跳过

下图就不是完全二叉树,在第4层不是从左往右一个一个铺的,跳过了一个结点

在这里插入图片描述



二叉树的存储,基本上使用的都是孩子表示法,这样只要拿到根结点,树中的所有结点就都能拿到了

写代码时,就是每个结点中创建两个引用left和right,通过left和right拿到当前结点的左右子结点,如果没有左子结点或没有右子结点,那就把对应的left和right的值设置为null

public class Node {
    private String val;
    private Node left;
    private Node right;

    public Node(String val) {
        this.val = val;
        this.left = null;
        this.right = null;
    }
}



创建一棵树也很简单,先创建下结点,再连接下结点即可,例如按照下图来创建二叉树,创建二叉树写在createTree方法中,方法返回根结点,因为拿到根结点,就能访问到整个树

在这里插入图片描述

public class Test {
    
    public static Node createTree(){
        Node a = new Node("A");
        Node b = new Node("B");
        Node c = new Node("C");
        Node d = new Node("D");
        Node e = new Node("E");
        Node f = new Node("F");
        Node g = new Node("G");
        a.left = b;
        a.right = c;
        b.left = d;
        b.right = e;
        e.left = g;
        c.right = f;
        return a;
    }
    public static void main(String[] args) {
       Node root = createTree();
    }
}

3,二叉树的遍历

拿到二叉树的根结点后,如何来遍历整个二叉树呢?
有三种方法,先序遍历,中序遍历,后序遍历,这三种方法都是基于递归来实现的,因此会稍微有点抽象
除了一些特殊情况,绝大部分二叉树这三种遍历的结点的打印顺序是不一样的

1,先序遍历

先序遍历就是分为三步:

  1. 先访问当前结点的值(打印)
  2. 递归的针对左子树进行先序遍历
  3. 递归的针对右子树进行先序遍历
    public static void preOrder(Node root){
        if(root == null){
            return;
        }
        System.out.println(root.val);
        preOrder(root.left);
        preOrder(root.right);
    }



接下来就画图来模拟下前序遍历的递归打印,这里博主把打印的按照顺序标了序号,初学的铁汁这部分一定要细品一下

就拿A来说,遍历时一定是preOrder(A.left)全部走完,才能轮到preOreder(A.right),那前序遍历打印的顺序就是ABDEGCF

在这里插入图片描述


在main方法中运行一下,确实是这个顺序

    public static void main(String[] args) {
       Node root = createTree();
       preOrder(root);
    }

在这里插入图片描述

2,中序遍历

中序遍历也是基于递归的方法,只是把访问当前结点的值放在了中间位置

中序遍历分为三步:

  1. 递归遍历左子树
  2. 访问当前结点的值(打印)
  3. 递归遍历右子树

中序遍历的方法名叫做inOrder,in就表示访问当前结点的操作在两个递归遍历操作之间

    public static void inOrder(Node root){
        if(root == null){
            return;
        }
        inOrder(root.left);
        System.out.printf(root.val + " ");
        inOrder(root.right);
    }



接下来就画图模拟下中序遍历二叉树的递归过程,中序遍历因为打印逻辑在中间,分析就会更抽象一些,就拿A结点来说,只有把inOrder(A.left)执行完,才能轮到打印A结点
初学的铁汁非常建议画图细品一下,这里打印的顺序就是DBGEACF

在这里插入图片描述



在main方法中运行下,也确实是这个顺序

    public static void main(String[] args) {
       Node root = createTree();
       inOrder(root);
    }

在这里插入图片描述

3,后序遍历

后序遍历就是把打印当前结点的操作放到最后,也是分为三步

  1. 递归遍历左子树
  2. 递归遍历右子树
  3. 打印当前结点



递归遍历的方法名就是postOrder,因为post有在…之后的意思

    public static void postOrder(Node root){
        if(root == null){
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        System.out.printf(root.val + " ");
    }



同样,画图来模拟下后序遍历的递归过程,后序遍历中,当前结点是最后打印的,要打印结点A,要等到postOrder(A.left)和postOrder(A.right)的逻辑都走完,才能执行打印语句

后序遍历的打印顺序就是DGEBFCA

在这里插入图片描述


在main方法中运行下,也确实是这个顺序

    public static void main(String[] args) {
       Node root = createTree();
       postOrder(root);
    }

在这里插入图片描述

4,层序遍历

层序遍历就是一层一层的打印,下面这个树的层序遍历结果就是ABCDEFG,先序、中序、后序遍历代码简单,但是理解起来不太好理解;层序遍历理解起来好理解,但是代码写起来就比较复杂

在这里插入图片描述



层序遍历代码的实现需要搭配队列,分为以下几步

  1. 创建一个存储元素类型为Node的队列,先把根结点入队列
  2. 循环的取出队首元素,访问这个元素的值(打印)
  3. 把这个元素的左子树和右子树都分别入队列
  4. 再回到第二步继续循环



如下图,先把A入队列,取出队首元素A,进行访问(也就是打印A),将A的左结点和右结点入队列

在这里插入图片描述


接着取出队首元素B,进行打印,将B的左结点和右结点入队列

在这里插入图片描述


再取出队首元素C,把C的左结点和右结点入队列,这样一直循环,就实现了层序遍历的效果

在这里插入图片描述


层序遍历的方法命名为levelOrder,代码如下所示:

    public static void levelOrder(Node root){
        if(root == null){
            return;
        }
        Queue<Node> queue = new ArrayDeque<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            Node cur = queue.poll();
            System.out.printf(cur.val + " ");
            if(cur.left != null){
                queue.offer(cur.left);
            }
            if(cur.right != null){
                queue.offer(cur.right);
            }
        }
    }

4,规律分析

在先序、中序、后序序列中,至少要得到两种序列,才能还原出二叉树,得到的这两种序列中必须包含中序序列,也就是只知道先序和后序序列是不行的

在408考试,以及后端开发的面试中,就会给出二叉树的两个序列,要求计算另一个序列,比如给出前序序列和中序序列,然后让计算后序序列,那就先还原出二叉树,再看着画出的二叉树计算出后序遍历的结果
要想快速根据两个序列还原出二叉树,就要分析清楚三种遍历的规律



首先分析一下先序遍历和后序遍历序列的规律

从根结点的角度来看,先序遍历的第一个元素就是根结点,后序遍历的最后一个元素就是根结点

在这里插入图片描述

从子树的角度来看,先序遍历中的子树部分(下图中用方框圈出的就是子树)的一个元素,就是子树的根结点;后序遍历中的子树部分的最后一个元素,就是子树的根结点

在这里插入图片描述



中序遍历,根结点在中间,根结点左侧的元素属于左子树,根结点右侧的元素属于右子树
从子树的角度来看,子树的根结点在中间,左侧元素属于是子树的左子树的中序遍历结果,右侧元素是子树的右子树的中序遍历结果

在这里插入图片描述



二叉树最核心的一个基本功,就是给出一个不是很复杂的二叉树,直接在脑子里想,分析按照某种模式遍历的结果(这个对于初学的铁汁可能有点难度,可以先多画几次图,接着再尝试在脑子里模拟递归的过程)

就拿下图中的二叉树,分析一下前序遍历打印结果,前序遍历就是先打印当前结点,再递归遍历左子树,再递归遍历右子树,这里博主就大概用文字的形式描述下分析的过程:
在这里插入图片描述

  1. 首先是打印结点A,接着递归遍历A的左子树,就遍历到了B,打印结点B
  2. 接着遍历B的左子树,遍历到了D,打印结点D,因为D结点的左右子树都为空,所以D结点这里就不再往下遍历了
  3. 这时候递归遍历B的左子树的操作已经完成,开始递归遍历B的右子树,遍历到了结点E
  4. 打印结点E,递归遍历E的左子树,遍历到了G,打印G,G结点这里也停止继续往下遍历
  5. E的左子树遍历完了,开始遍历E的右子树,右子树为空,停止继续往下遍历,E结点遍历完成
  6. E结点遍历完成后,B结点的左右子树就也遍历完成,进而说明A结点的左子树遍历完成
  7. A结点的左子树遍历完成后,开始遍历右子树,遍历到了C结点,打印C,再遍历C的左子树,左子树为空,停止继续往下遍历
  8. C的左子树遍历完成后,开始遍历右子树,遍历到F,打印F,F的左右子树都为空,不再继续往下遍历
  9. 至此C的左右子树遍历完成,也就说明A的右子树遍历完后,整个子树先序遍历也就完成了

这里是一边分析,一边写的,第1步分析完后,就直接把AB写下来,分析到第二步打印结点D,把D写下来,写到AB后面;以此类推,分析完成就能得到前序遍历结果

中序遍历和后序遍历博主就不再用大量文字描述分析过程了,初学的铁汁如果感觉这部分比较抽象,可以自己多画几次递归图,结合递归图分析下,慢慢的就能直接在脑子里模拟出过程



下面这四道题目,铁汁们可以做一下

在这里插入图片描述



第一题,题目中已经指出是完全二叉树(也就是一层一层从左往右铺的,铺的时候不能跳过),又给出了层序遍历的结果,那我们就可以先画出二叉树(如下图),再看图得到前序遍历后的结果,就是ABDHECFG

在这里插入图片描述


第二题很简单,先序遍历结果的第一个元素就是根结点,因此二叉树的根结点就是E



第三个题目特别经典,就是给出两种序列,让我们还原二叉树,接着推出另一种序列,这种题目一般给出的序列长度都比较短,这里给出中序遍历序列为badce,后序遍历序列为bdeca,根据这两个序列还原二叉树,就要用到前面分析的规律了

通过后序序列可以知道a为根结点,再看中序序列,a左边的元素(b)属于左子树,a右边的元素(dce)属于右子树

再把d、c、e三个元素单独拿出来看,推断下这三个元素组成的子树的形状,后序序列中,这三个元素的顺序是dec,也就说明c是这个子树根结点;在中序序列中,这三个元素的顺序是dce,c在中间且c为根结点,就说明d是c的左节点,e是c的右结点,这样就可以得到dce的形状结构(做这类题目的解法就是把几个元素拿出来单独分析,分析组成的树结构),树结构如下图,得出前序遍历的结果就是abcde

在这里插入图片描述




第4个题目,后序遍历和中序遍历的结果都是ABCDEF,解法跟第三题是类似的,这种两个序列结果一样的,反而特别简单

可以推出F是树的根结点,中序序列是ABCDEF,就说明F右子树是空的,ABCDE都在左子树上

接着再看A、B、C、D、E这5个元素,分析这5个元素构成的树结构,会发现E是这个子树的根结点,E还是没有右子树,ABCD都在左子树上

再分析A、B、C、D这四个元素,以此类推,铁汁们就会发现这个树结构就是一条线,后面的就不用再一 一分析了🐵,层序遍历的结果就是FEDCBA

在这里插入图片描述

结语

  • 二叉树的学习算是数据结构中的一个难点,并不是二叉树本身的结构复杂,而是二叉树的方法基本上都涉及递归,就比如遍历方式,顺序表、链表、栈、堆都是通过循环来遍历的,二叉树这里变成了通过递归来遍历

  • 包括树结图,脑子中想出二叉树的遍历序列,这个初学时可能会感觉比较抽象,最好的方法就是在画图板上多画几次递归过程图,细品下递归过程,这样分析递归的能力就一定会提升



以上就是今天的所有内容啦~完结撒花~🥳🎉🎉

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值