【java数据结构】二叉树


此篇博客希望对你有所帮助(帮助你了解二叉树,二叉树主要运用递归,有那块代码不懂或者理解不了的,一定要进行画图去理解),不懂的或有错误的也可在评论区留言,错误必改评论必回!!!持续关注,下一篇博客是二叉树面试题!!!整篇博客的代码都在Gitee中(代码链接放在文章结尾)。

一、 认识二叉树

1.1 二叉树的概念

二叉树是一个有限的节点集合,这个集合可能是空的(此时称为空二叉树),或者由一个根节点以及两个互不相交的、分别被称为左子树和右子树的二叉树所组成。

1.2 二叉树的特性:

  1. 每个节点至多有两个子节点,分别被称为左子节点和右子节点。
  2. 左子树和右子树的次序不能颠倒。
  3. 二叉树是有序树,因为即使树中节点没有值,其左右子树的区分也仍然有意义。

在这里插入图片描述

对于任意二叉树都是由以下几种情况复合而成:
在这里插入图片描述

1.3 两种特殊的二叉树:

  1. 满二叉树: 一棵二叉树,如果每层的结点数都达到最大值,则这棵二叉树就是满二叉树。也就是说,如果一棵二叉树的层数为K,且结点总数是(2^k)-1 ,则它就是满二叉树。
  2. 完全二叉树: 完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。 要注意的是满二叉树是一种特殊的完全二叉树。

在这里插入图片描述

1.4 二叉树的性质:

二叉树(Binary Tree)具有一些重要的性质,这些性质对于理解和操作二叉树非常有帮助。下面是二叉树的一些常见性质:

  • 每个节点最多有两个子节点:每个节点最多有一个左子节点和一个右子节点。这意味着一个节点的度数(Degree)最大为2。
  • 左子树和右子树的顺序是固定的:在二叉树中,左子树位于父节点的左侧,右子树位于父节点的右侧。改变左右子树的顺序将产生不同的二叉树。
  • 节点的度数:节点的度数是指该节点的子节点数量。二叉树中,节点的度数最大为2,即一个节点最多有两个子节点。
  • 叶节点(叶子节点):叶节点是没有子节点的节点,也可以称为终端节点。
  • 节点的层级(Level):根节点的层级为1,其余节点的层级为其父节点的层级加1。
  • 树的高度(Height):树的高度是从根节点到最远叶节点的最长路径上的边数。叶节点的高度为0,空树的高度为-1。
  • 完全二叉树(Complete Binary Tree)性质:在完全二叉树中,除了最后一层外,其他层的节点都是满的,且最后一层的节点都尽量靠左排列。
  • 满二叉树(Full Binary Tree)性质:在满二叉树中,除了叶节点外,每个节点都有两个子节点。
  • 二叉搜索树(Binary Search Tree)性质:二叉搜索树是一种特殊的二叉树,它满足以下性质:对于树中的每个节点,其左子树中的所有节点都小于该节点的值,而其右子树中的所有节点都大于该节点的值。
  • 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2^(i-1) (i > 0)个结点。
  • 若规定只有根结点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 (2^k) - 1 (k>=0)。
  • 对任何一棵二叉树, 如果其叶结点个数为 n0,度为2的非叶结点个数为 n2,则有 n0 = n2 + 1。
  • 具有n个结点的完全二叉树的深度k为log2 (n + 1) 上取整。
  • 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为 i 的结点有:
    • 若 i > 0,双亲序号:(i - 1) / 2;i = 0,i 为根结点编号,无双亲结点。
    • 若 2i + 1< n,左孩子序号:2i + 1,否则无左孩子。
    • 若 2i + 2 < n,右孩子序号:2i + 2,否则无右孩子。

1.5 二叉树的存储:

  1. 链式存储法(链表表示法)
    链式存储法使用节点(Node)来存储二叉树的每个元素,每个节点包含三个部分:

数据域(data):存储节点的值。
左指针(left):指向左子节点。
右指针(right):指向右子节点。

这种方法的优点是可以灵活地表示任意形状的二叉树,节省空间(不需要为空的子节点分配空间)。缺点是需要额外的指针空间,并且遍历二叉树时可能涉及频繁的指针操作。

  1. 顺序存储法(数组表示法)
    顺序存储法使用一个数组来存储二叉树的节点。对于完全二叉树,节点在数组中的位置可以通过以下公式确定:

对于根节点,其索引为 0。
对于节点索引为 i 的左子节点,其索引为 2i + 1。
对于节点索引为 i 的右子节点,其索引为 2
i +2。

这种方法的优点是操作简单,通过索引关系可以直接访问任意节点的子节点和父节点。缺点是对于非完全二叉树,会浪费空间(因为数组中需要填充空节点)。

二、 实现二叉树

2.1 二叉树节点的定义:

    static class TreeNode{
        public char val;
        public TreeNode left;//左孩子的引用
        public TreeNode right;//右孩子的引用

        public TreeNode(char val) {
            this.val = val;
        }
    }

2.2 二叉树的基本操作:

    // 获取树中节点的个数
    int size(TreeNode root){

    }
    // 获取叶子节点的个数
    int getLeafNodeCount(TreeNode root){

    }
    // 子问题思路-求叶子结点个数
// 获取第K层节点的个数
    int getKLevelNodeCount(TreeNode root,int k){

    }
    // 获取二叉树的高度
    int getHeight(TreeNode root){

    }
    // 检测值为value的元素是否存在
    TreeNode find(TreeNode root, int val){

    }

    // 判断一棵树是不是完全二叉树
    boolean isCompleteTree(TreeNode root){

    }
获取树中节点的个数

代码思路:
1.判断数是否为空。为空节点个数为0。
2.定义一个全局int型变量len,因为需要递归,如果将len定义为局部变量,在每次递归时len都是我们给定的初始值。

    // 获取树中节点的个数
    public int len = 0;

    int size(TreeNode root) {
        if (root == null) {
            return 0;
        }
        len++;
        size(root.left);
        size(root.right);
        return len;
    }
获取叶子节点的个数

代码思路:
两种解决方案:
1.先去判断树是否为空
(1)
1.定义一个全局变量size,和上面思路一样。
2.通过叶子节点的概念,我们熟知,我们需要递归每一个节点,然后去判断节点的度是否为0,为0,则是叶子节点,否则,反之。
(2)
1.不去定义全局变量,直接递归。
2.递归到每个节点都去判断一次节点的度是否为0,如果为0,则返回到上次递归语句中。

    // 获取叶子节点的个数
    public int size = 0;

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

    int getLeafNodeCount2(TreeNode root) {
        if (root == null) return 0;
        if (root.left == null && root.right == null) {
            return 1;
        }
        return getLeafNodeCount2(root.left) + getLeafNodeCount2(root.right);
    }
获取第K层节点的个数

代码思路:
1.先去判断代码是否为空
2.因为是获得第K层的节点个数,我们可以采用逆序的思想:
将根那一层当成第K层,然后进行递归,每递归一次K-1,当递归到K==1时,此时就是我们想要得到的第K层

// 获取第K层节点的个数
    int getKLevelNodeCount(TreeNode root, int k) {
        if (root == null) {
            return 0;
        }
        if (k == 1) {
            return 1;
        }
        return getKLevelNodeCount(root.left, k - 1) + getKLevelNodeCount(root.right, k - 1);
    }
获取二叉树的高度

代码思路:
1.先判断树是否为空
2.分别对每个节点进行左递归和右递归
3.判断这个节点左递归和右递归那个递归的次数多,返回那个值,最后返回的值就是树的高度。

    //    // 获取二叉树的高度
    int getHeight(TreeNode root) {
        if (root == null) return 0;
        int leftH = getHeight(root.left);
        int rightH = getHeight(root.right);
        return leftH > rightH ? leftH + 1 : rightH + 1;
    }

检测值为value的元素是否存在

代码思路:
1.先判断树是否为空
2.然后判断根节点是否为value值,如果不是,则进行递归
3.如果找到value值,则需要定义一个节点ret来接受找到的value值的节点,进行一步一步返回。这里返回的将一直是找到的ret节点

//    // 检测值为value的元素是否存在
    TreeNode find(TreeNode root, char val){
        if(root==null){
            return null;
        }
        if(root.val==val){
            return root;
        }
        TreeNode ret=find(root.left,val);
        if(ret!=null){
            return ret;
        }
       ret= find(root.right,val);
        return ret;
    }
判断一棵树是不是完全二叉树

代码思路:
1.这里需要借助所学的队列(先进先出)
2.首先判断树是否为空
3.将根节点先存入到队列当中,这里需要定义一个cur来标记出队列的节点
4.当出一个节点,就将这个节点的左右孩子节点存入队列中,不管左右孩子是否为空
这里值得注意的是:存入队列当时中的节点为null,queue.isEmpty()不为空的(判断 的是队列中的元素个数)
5.当cur为null时,跳出queue.isEmpty()循环,这个时候判断队列当中是否还有除了nul,以为的其他元素,如果有其他元素,则这就不是一个完全二叉树,反之,则不是完全二叉树。

//    // 判断一棵树是不是完全二叉树
    boolean isCompleteTree(TreeNode root){
        Queue<TreeNode> queue=new LinkedList<>();
        if(root==null){
            return true;
        }
        queue.offer(root);
        TreeNode cur;
        while(!queue.isEmpty()){
            cur=queue.poll();
            if(cur!=null) {
                queue.offer(cur.left);
                queue.offer(cur.right);
            }else{
                break;
            }
        }
        while(!queue.isEmpty()){
            if(queue.peek()==null){
                queue.poll();
            }else{
                return false;
            }
        }
        return true;
    }

完全二叉树图解:
在这里插入图片描述
非完全二叉树图解:
在这里插入图片描述

2.3 二叉树的遍历:

遍历是访问树中所有节点的过程,主要有以下几种方式:

  • 前序遍历(Preorder Traversal):根节点 -> 左子树 -> 右子树
  • 中序遍历(Inorder Traversal):左子树 -> 根节点 -> 右子树(对于二叉搜索树,这是升序访问所有节点的方式)
  • 后序遍历(Postorder Traversal):左子树 -> 右子树 -> 根节点
  • 层次遍历(Level Order Traversal):按层次从上到下、从左到右访问节点(通常使用队列实现)。

举例:
在这里插入图片描述
前序遍历:A B C D E F
中序遍历:C B D A E F
后序遍历 :C D B F E A
层次遍历 :A B E C D F

前序遍历
   // 前序遍历
    void preOrder(TreeNode root){
        if(root==null){
            return;
        }
        System.out.println(root.val+" ");
        preOrder(root.left);
        preOrder(root.right);
    }
中序遍历
    // 中序遍历
    void inOrder(TreeNode root){
        if(root==null){
            return;
        }
       inOrder(root.left);
        System.out.println(root.val+" ");
        inOrder(root.right);
    }
后序遍历
// 后序遍历
    void postOrder(TreeNode root){
        if(root==null){
            return;
        }
        postOrder(root.left);
        postOrder(root.right);
        System.out.println(root.val+" ");
    }
层序遍历

代码思路:
1.需要借助对列来实现。
2.首先先判断树是否为空。
3.不为空,先将根节点存进去,然后定义一个节点cur来接受从队列中出来的节点,判断弹出的节点左右孩子节点是否为空,不为空的存入到队列当中,打印弹出的元素。

//    //层序遍历
    void levelOrder(TreeNode root){
        Queue<TreeNode> queue=new LinkedList<>();
        if(root==null){
            return;
        }
        queue.offer(root);
        TreeNode cur;
        while(!queue.isEmpty()){
            cur=queue.poll();
            if(cur.left!=null){
                queue.offer(cur.left);
            }
            if(cur.right!=null){
                queue.offer(cur.right);
            }
            System.out.print(cur.val+" ");
        }
    }

此篇博客的代码!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值