【Java 数据结构】树和二叉树

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🦾🦾🦾 


目录

1. 树形结构

1.1 树的概念

1.2 树的表示形式(简单了解)

2、二叉树 (重点)

2.1 二叉树的概念

2.2 特殊的二叉树

2.3 二叉树的性质(重点,选择题常考)

2.4 二叉树性质相关习题

2.5 与二叉树遍历相关的题目

3、实现二叉树的基本操作

3.1 了解二叉树的存储结构

3.2实现二叉树的基本操作

1. 前提说明​

2. 二叉树的前中后序遍历 

3.  二叉树的层序遍历:这里需要借助队列完成

4. 获取二叉树中结点的个数

5. 获取二叉树中叶子结点的个数

6. 获取二叉树中第k层结点的个数

7. 获取二叉树的高度

8. 查找值为val的结点并返回

9. 判断一棵树是否为完全二叉树(重点,常考)



1. 树形结构

1.1 树的概念

树是一种非线性的数据结构,它是由n个(n>=0)个有限节点组成一个具有层次关系的集合。它的形状像一颗倒挂的树,根在上,叶在下。

特点:

· 有一个特殊的结点称为根节点,根节点没有前驱结点

· 除根节点外,其余结点被分成M(M>0)个互不相交的集合T1,T2,.....,Tm,其中每一个集合又是一颗与树类似的字树。每棵子树的根节点有且只有一个前驱,可以没有或者多个后继

· 树是递归定义的

注意:在树形结构中,子树不能有交集,否则就不是树形结构

重要概念:

一个N个节点的树有N-1条边

结点的度:一个结点含有子树的个数

树的度所有结点的度的最大值称为树的度

叶子结点或终端结点度为0的结点

双亲结点或父亲结点若一个结点含有子节点,则这个结点为其子结点的双亲结点

孩子结点或子结点一个结点含有的子树的根结点称为该结点的子结点

根结点树中没有双亲结点的结点

结点的层次从根开始定义,根为第一层,根的子结点为第二层,以此类推

树的高度或深度树中结点层次的最大值

森林由m(m>0)棵互不相交的树组成的集合称为森林

1.2 树的表示形式(简单了解)

树的结构相对于线性表比较复杂,要存储起来也比较麻烦,这里有几种表示方法:双亲表示法,孩子表示法,孩子双亲表示法,孩子兄弟表示法等,这里只简单了解最常用的孩子兄弟表示法。

孩子兄弟表示法: 

class Node{
    int val;          //存储的数据
    Node firstChild;  // 第一个孩子引用,一般称之为左结点,Node left
    Node nextBrother;   //下一个兄弟引用,一般称之为右结点,Node right
}

2、二叉树 (重点)

2.1 二叉树的概念

二叉树是一个有限的集合,该集合为空,或者是由一个根节点和两颗子树构成,分别为左子树和右子树,只含有一个根节点的也可也称为二叉树。

 注意:

  • 二叉树不存在度大于2的节点
  • 二叉树的子树有左右之分
  • 每个子树的根节点往下都可看作一个新的二叉树
  • 空树和只有一个节点的树都可以称为二叉树
  • 根节点只有左树(或右树)并满足节点度不大于2的情况下,也是二叉树


2.2 特殊的二叉树

这里有个问题,前面学习的 Stack 和 ArrayList 需要判断满的情况并扩容,那么二叉树可能出现满的情况吗?显然不会,因为二叉树是由节点构造而成的,但是如果每层的节点数都达到了最大值,那么这棵树就是满二叉树。换句话说,如果一颗二叉树的层数为k,且总结点的个数是2^k-1,那么就是满二叉树。满二叉树图例:

2.完全二叉树:它是一种效率很高的数据结构,完全二叉树是由满二叉树引出来的。对于深度为k,有n个结点的二叉树,当且仅当每一个结点都与深度为k的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树,满二叉树是一种特殊的完全二叉树 


2.3 二叉树的性质(重点,选择题常考)

性质1: 如果规定根节点的层数为1,那么一颗非空的二叉树的第 k 层上最多有 2^(k-1) 个节点 k>0。

性质2: 如果规定只有根节点的二叉树的深度为 1,则深度为 k 的二叉树的最大节点数是 2^k - 1(k >= 0)。

性质3: 对于任何一棵二叉树,如果叶子(度为0)节点的个数为 n0,度为2的非叶子节点的个数为 n2,则 n0 = n2 + 1。

性质4: 具有 n 个节点的完全二叉树的深度 k 为 log(n+1) 上取整。(以2为底)

性质5: 对于具有n个节点的完全二叉树,如果从上至下,从左至右的顺序对所有的节点从 0 开始进行编号,如果父节点下标为 i,左孩子节点下标为:2 * i + 1 且 < n右孩子下标为:2 * i + 2 且 < n已知孩子节点下标,求父节点:(i - 1) / 2 = 父节点下标,若 i = 0,则 i 为根节点编号。 

推导性质3: 


2.4 二叉树性质相关习题

1. 某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )

A.不存在这样的二叉树    B.200    C.198    D.199

题解: 这道题我们可以运用上面的二叉树的性质3,任意一颗二叉树中,度为2的节点比度为0的节点少一个,那题目告诉我们有 199 个度为 2 的节点,所以度为 0 的节点就是 199 + 1,本题选 A


 2.在具有 2n 个结点的完全二叉树中,叶子结点个数为( )

A.n    B.n+1    C.n-1    D.n/2

题解:因为二叉树不存在度大于 2 的节点,因此我们可知,度为0的节点 + 度为1的节点 + 度为2的节点 = 2n。 设度为 0 的节点为 n0,度为 1 的节点为 n1,度为 2 的节点为 n2,所以:n0 + n1 + n2 = 2n。得出了这个公式,后面就好办了,我们看图:


 3.一个具有767个节点的完全二叉树,其叶子节点个数为()

A.383    B.384    C.385    D.386 

题解:这道题跟上一道题思路类似,同样可以设:度为 0 的节点为 n0,度为 1 的节点为 n1,度为 2 的节点为 n2, 那么是不是得出:767 = n0 + n1 + n2,后面岂不是好办了吗?直接看图:


 4.一棵完全二叉树的节点数为531个,那么这棵树的高度为( )

A.11    B.10    C.8    D.12 

这个题就比较简单了, 运用上面二叉树的性质2,即:531 = 2^k - 1,532 = 2^k

k等于多少?当k等于9时,2^9 = 512,即k=9当前完全二叉树最大节点数为512小于531,不满足题意,当k等于10时,2^10 = 1024,满足题意,所以本题选 B!


2.5 与二叉树遍历相关的题目

知识点补充:

已知前序遍历和中序遍历求后序遍历,可以通过前序遍历第一个元素开始依次拿到每一个根节点,通过根节点去中序遍历中划分左子树和右子树,重复上述操作得到二叉树即可求出后序遍历.

已知后序遍历和中序遍历求前序遍历,可以通过后序遍历的最后一个元素开始依次拿到每一个根节点,通过根节点去中序遍历中划分左子树和右子树,重复上述操作得到二叉树即可求出前序遍历.

题目一

  1. 某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为( A )
    A. ABDHECFG
    B. ABCDEFGH
    C. HDBEAFCG
    D. HDEBFGCA

题目二

  1. 二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为( A )
    A. E
    B. F
    C. G
    D. H

题目三

  1. 设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为( D )
    A. adbce
    B. decab
    C. debac
    D. abcde

        图解:

 题目四

  1. 某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为( A )
    A. FEDCBA
    B. CBAFED
    C. DEFCBA
    D. ABCDEF

3、实现二叉树的基本操作

3.1 了解二叉树的存储结构

二叉树的存储结构分为顺序存储和链式存储,顺序存储后续讲解优先级队列会讲,链式存储跟前面的链表还是有一定区别的。

二叉树的链式存储也是由一个个节点构成的,通常采用二叉链和三叉链(平衡二叉树...) 

// 孩子表示法
public class TreeNode {
    private char val; //数据域
    private TreeNode left; //左孩子的引用,以左孩子为根的整棵树
    private TreeNode right; //右孩子的引用,以右孩子为根的整棵树
}
 
// 孩子双亲表示法
public class TreeNode {
    private char val; //数据域
    private TreeNode left; //左孩子的引用,以左孩子为根的整棵树
    private TreeNode right; //右孩子的引用,以右孩子为根的整棵树
    private TreeNode parent; //当前节点的根节点的引用
}

3.2实现二叉树的基本操作

 1. 前提说明

 从图结合概念可以看出,二叉树是递归定义的,后面基本操作都是按照该概念实现的 

我们需要先创建一颗二叉树,这里手动快速创建一颗简单的二叉树:

public class MyTreeBlog {
    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        public TreeNode (int val){
            this.val = val;
        }
    }
    private BTNode root;
    public void createBinaryTree(){
        TreeNode node1 = new TreeNode (1);
        TreeNode node2 = new TreeNode (2);
        TreeNode node3 = new TreeNode (3);
        TreeNode node4 = new TreeNode (4);
        TreeNode node5 = new TreeNode (5);
        TreeNode node6 = new TreeNode (6);
        root = node1;
        node1.left = node2;
        node1.right = node4;
        node2.left = node3;
        node4.left = node5;
        node4.right = node6;
    }
}

注意:上述代码不是创建二叉树的方式,创建二叉树后面会介绍

2. 二叉树的前中后序遍历 

遍历就是指沿着某条搜索路线,依次对树中每个结点均做一次且仅做一次访问,访问结点所做的操作依赖于具体的应用问题(比如:打印结点内容),遍历是二叉树最重要的操作之一,是二叉树上进行其他运算的基础。

N代表根结点,L代表根结点的左子树,R代表根节点的右子树,则根据遍历根节点的先后次序有以下几种遍历方式:(三种遍历方式的区别在于打印/访问根节点的时机不同)

NLR:前序遍历:根节点---根的左子树---根的右子树 (每次访问直接打印根节点)

LNR:中序遍历:根的左子树---根结点---根的右子树 (左子树走完返回时才打印根节点)

LRN:后序遍历:根的左子树---根的右子树---根结点 (左右子树都走完返回时才打印根节点)

知识点补充:当一个方法执行完毕(到达方法体的末尾或遇到return语句),它也会将控制权返回给调用它的方法。

         实现代码:

// 前序遍历 -> 根 左子树 右子树
public void preOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    // 碰到根节点就打印
    System.out.print(root.val + " ");
    // 遍历左子树
    preOrder(root.left);
    // 遍历右子树
    preOrder(root.right);
}

// 中序遍历 -> 左子树 根 右子树
public void inOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    // 遍历左子树
    inOrder(root.left);
    // 打印根节点
    System.out.print(root.val + " ");
    // 遍历右子树
    inOrder(root.right);
}
 
// 后序遍历 -> 左子树 右子树 根
public void postOrder(TreeNode root) {
    if (root == null) {
        return;
    }
    // 遍历左子树
    postOrder(root.left);
    // 遍历右子树
    postOrder(root.right);
    // 打印根节点
    System.out.print(root.val + " ");
}

        前序遍历画图演示:(打印结果ABCDEF)

 由这个递归展开图相信也能看明白,碰到根节点就打印,然后就去遍历当前根的左子树,如果实在不理解,就把博主的代码粘贴下去画递归展开图,多画几遍,你就能慢慢理解递归了! 

3.  二叉树的层序遍历:这里需要借助队列完成

        解题思路:采用非递归的方式,思路是这样的,定义一个队列,先将根节点入队,如果队列不为空,弹出一个队头元素并打印,接着再去看看它左树和右树的根节点是否为空,如果不会空都入队,重复上述操作,当队列为空时,层序遍历结束。

        图示:      

        实现代码:

//层序遍历
 public void levelOrder(TreeNode root){
        if(root==null){
            return;      //根为空直接返回
        }
        Queue<BTNode> q = new LinkedList<>();
        q.offer(root);    //先将根入队列    
        while(!q.isEmpty()){      //队列不为空时,循环
            BTNode cur = q.poll();   //根出队列
            System.out.print(cur.val+" ");
            if(cur.left!=null){     //根有左子树,将左子树的根入队列
                q.offer(cur.left);
            }
            if(cur.right!=null){    //根有右子树,将右子树的根入队列
                q.offer(cur.right);
            }
        }
        System.out.println();
    }

 4. 获取二叉树中结点的个数

二叉树结点的个数=根的左子树结点的个数+根的右子树结点的个数+1(这个1就是根节点本身),所以直接一个递归就解决问题了

     子问题思想: ( 时间复杂度O(N),空间复杂度O(logN) )

 public int size(TreeNode root){
        if(root==null){
            return 0;
        }
        return 1+size(root.left)+size(root.right);
    }

     遍历思想:

public int usedSize; 
public int size(TreeNode root){
    if (root == null) {
        return;
    }
    usedSize++;
    size(root.left);
    size(root.right);
}

5. 获取二叉树中叶子结点的个数

叶子结点就是该结点的左子树为空,右子树为空,所以当遇到此节点时返回1,递归返回所有该结点的总数

public int getLeafNode(TreeNode root){
        if(root==null){
            return 0;
        }
        if(root.left == null && root.right == null){
            return 1;
        }
        return getLeafNode(root.left)+getLeafNode(root.right);
    }

6. 获取二叉树中第k层结点的个数

求第k层节点的个数,我们可以用子问题的思想去求,分别去求它左右子树k-1层的节点个数即可

 public int getLevelNode(TreeNode root,int k){
        if(root==null||k<0){  //判断参数
            return 0;
        }
        if(k==1){        //如果k==1,则只有根返回1
            return 1;
        }
        //递归
        return getLevelNode(root.left,k-1) + getLevelNode(root.right,k-1);
    }

         递归过程演示:

7. 获取二叉树的高度

 将此二叉树的左子树的高度与右子树的高度进行比较,较大的高度+1就是此二叉树的高度

 public int height(TreeNode root){
        if(root==null){
            return 0;
        }
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);
        return (Math.max(leftHeight,rightHeight)+1);
    }

         递归过程演示:

8. 查找值为val的结点并返回

先递归在左子树中找,再递归在右子树中找 

public BTNode find(TreeNode root,int val){
        if(root==null){
            return null;
        }
        if(root.val == val){
            return root;
        }
        BTNode leftTree = find(root.left,val); //递归在左子树中找
        if(leftTree != null){
            return leftTree;     //找到了返回
        }
        BTNode rightTree = find(root.right,val);   //递归在右子树中找
        if(rightTree != null) {
            return rightTree;
        }
        return null;
    }

        递归过程演示: 

9. 判断一棵树是否为完全二叉树(重点,常考)

利用完全二叉树的性质, 如果中间下标位置的结点有空缺, 说明不是完全二叉树

所以对这棵树进行层序遍历, 利用队列这种数据结构, 根节点入队, 当队列不为空时进入循环, 出队一次, 并让它的左右孩子结点入队(队列可以offer(null), 优先级队列不可以), 出队时遇见 null, 退出循环

退出循环后检查队列中剩余数据是否还有非空数据, 如果存在非空数据说明不是完全二叉树

   实现代码:

public boolean isCompleteTree(TreeNode root) {
    // 如果根节点为空,则返回 true,表示空树也是完全二叉树
    if (root == null) {
        return true;
    }
    
    // 创建一个队列,用于存储待处理的节点
    Queue<TreeNode> queue = new LinkedList<>();
    
    // 将根节点加入队列
    queue.offer(root);
    
    // 当队列不为空时,说明还有节点需要处理
    while (!queue.isEmpty()) {
        // 弹出队列中的一个节点
        TreeNode cur = queue.poll();
        
        // 如果当前节点不为空
        if (cur != null) {
            // 将当前节点的左孩子和右孩子加入队列
            queue.offer(cur.left);
            queue.offer(cur.right);
        } else {
            // 如果当前节点为空,说明已经遍历到了完全二叉树的边界,退出循环
            break;
        }
    }
    
    // 在遍历完所有节点后,检查队列中剩余的节点是否都为空
    while (!queue.isEmpty()) {
        // 弹出队列中的一个节点
        TreeNode tmp = queue.poll();
        
        // 如果队列中有非空节点,说明这棵树不是完全二叉树,返回 false
        if (tmp != null) {
            return false;
        }
    }
    
    // 如果队列中所有节点都为空,说明这棵树是完全二叉树,返回 true
    return true;
}

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书生-w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值