Java二叉树超详解(常用方法介绍)(2)

简介: Java二叉树超详解(常用方法介绍)(2)

二叉树中的常用方法

静态二叉树的手动创建

这里我们先给出二叉树结点的信息(这里是内部类):

static class TreeNode {
        public char val;
        public TreeNode left;//左孩子的引用
        public TreeNode right;//右孩子的引用
 
        public TreeNode(char val) {
            this.val = val;
        }
    }

手动创建的代码如下:

/**
     * 创建一棵二叉树 创建成功后 返回根节点
     * @return
     */
    public TreeNode createTree() {
        TreeNode A = new TreeNode('A');
        TreeNode B = new TreeNode('B');
        TreeNode C = new TreeNode('C');
        TreeNode D = new TreeNode('D');
        TreeNode E = new TreeNode('E');
        TreeNode F = new TreeNode('F');
        TreeNode G = new TreeNode('G');
        TreeNode H = new TreeNode('H');
 
        A.left = B;
        A.right = C;
        B.left = D;
        B.right = E;
        C.left = F;
        C.right = G;
        E.right = H;
 
        return A;
    }

利用new出结点对象和左右指针的连接,我们就可以创建一棵这样的简单二叉树。(给你们画了个图嘿嘿)。

获取树中结点的个数

这里依然是用到了树的递归的思想:我们可以使用递归左树右树的方法,利用一个常量来记录结点的个数。

 public static int nodeSize;
 
    /**
     * 获取树中节点的个数:遍历思路
     */
    void size(TreeNode root) {
        if(root == null) {
            return;
        }
        nodeSize++;
        size(root.left);
        size(root.right);
    }

但是更推荐用子问题的思想,即:一棵树的结点个数 = 左树的结点个数 + 右数的节点个数+ 根结点(即1),像这种利用递归方法的返回值计算我们就称为子问题思路。代码如下:

 /**
     * 获取节点的个数:子问题的思路
     *
     * @param root
     * @return
     */
    int size2(TreeNode root) {
        if(root == null) {
            return 0;
        }
        return size2(root.left) + size2(root.right) + 1;
    }

获取叶子结点的个数

求叶子结点,在代码上,就是求含有多少左边和右边都是null的节点数(如果一个节点满足这样的条件就++),像上面一样,先给出遍历的方法:

/*
     获取叶子节点的个数:遍历思路
     */
    public static int leafSize = 0;
 
    void getLeafNodeCount1(TreeNode root) {
        if(root == null) {
            return;
        }
        if(root.right == null && root.left == null) {
            leafSize++;
        }
        getLeafNodeCount1(root.left);
        getLeafNodeCount1(root.right);
    }

下面是子问题的思路:

 /*
     获取叶子节点的个数:子问题
     */
    int getLeafNodeCount2(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftCount = getLeafNodeCount2(root.left);
        int rightCount = getLeafNodeCount2(root.right);
//满足条件:即左右返回值+1, 不满足条件:还是给出原来的左右返回值
        return root.right == null && root.right == null ? leftCount + rightCount + 1 : leftCount + rightCount;
    }

获取一棵树第k层的节点数目

还是主要利用递归的思想:一棵树第k层的结点数 = 左树第k-1层的节点数 + 右树第k-1层的节点数。递归的截止条件就是:当k为1时,直接返回1即可,如果为空则返回0。那么所以,代码如下:

/*
    获取第K层节点的个数
     */
    int getKLevelNodeCount(TreeNode root, int k) {
        if(root == null) {
            return 0;
        }
        if(k == 1) {
            return 1;
        }
        //获取左树第k-1层的节点数
        int leftCount = getKLevelNodeCount(root.left, k - 1);
        //获取右树第k-1层的节点数
        int rightCount = getKLevelNodeCount(root.right, k - 1);
        //返回左右树之和
        return leftCount + rightCount;
    }

获取二叉树的深度

还是利用递归的思想(截止条件:为空返回零):一棵二叉树的深度 = 左树和右树之中最高的那一个的深度 + 1.

利用我们之前学的子问题思想和递归,可以轻松写出:

int getHeight(TreeNode root) {
        if(root == null) {
            return 0;
        }
        int leftHeight = getHeight(root.left);
        int rightHeight = getHeight(root.right);
        //返回深度最大的那一个的深度 + 1
        return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
    }

检查树中是否有值为value的结点(即返回该节点)

还是利用之前的递归思路:value可能在当前结点,也可能在左右树当中,亦有可能不存在。

这里一定要注意一个点:如果已经提前找到了目标节点,就直接返回即可(比如在左树当中找到了,就不用在右树当中找了),这样做可以大大节约时间

 // 检测值为value的元素是否存在
    TreeNode find(TreeNode root, char val) {
        if(root == null) {
            return null;
        }
        if(root.val == val) {
            return root;
        }
        TreeNode ret1 = find(root.left, val);
        //不再去右边了
        if(ret1 != null) {
            return ret1;
        }
        TreeNode ret2 = find(root.right, val);
        if(ret2 != null) {
            return ret2;
        }
        return null;
    }

层序遍历

层序遍历,在上一篇中我也有讲,就是对一棵树进行从上到下,从左到右的遍历操作。那么如何通过代码来实现这一个过程呢?这时我们就可以用到之前的一个数据结构,也就是队列,那么我们如何利用队列来进行层序遍历呢,我们来看下面:

这里注意一个点,当且仅当左/右儿子不为空时,才能批准放入队列,否则跳过。代码如下:

 

 //层序遍历(利用队列)
    void levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        //当队列不为空时持续该过程
        while(!queue.isEmpty()) {
            TreeNode cur = queue.poll();
            System.out.print(cur.val + " ");
            if(cur.left != null) {
                queue.offer(cur.left);
            }
            if(cur.right != null) {
                queue.offer(cur.right);
            }
        }
    }

判断一棵树是否为完全二叉树

我们来回顾一下完全二叉树的性质:即完全二叉树是一棵从上到下,从左到右依次排列元素的树(即直到最后一个结点之后,其余前面的结点都不能为空)。所以我们就浮现出了这样的思路,即还是利用队列来遍历树每次,都将左右儿子放入队列(不论是不是空),当遇到了第一个为空的元素后,立刻停止放入,检查队列中剩余的元素是否有非空的,如果全是null,则是完全二叉树,如果有一个不是,就不是完全二叉树,画图如下:

代码如下:

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

 

相关文章
|
1月前
|
Java 开发者
Java 中的 toString() 方法详解:为什么它如此重要?
在Java开发中,`toString()`方法至关重要,用于返回对象的字符串表示。默认实现仅输出类名和哈希码,信息有限且不直观。通过重写`toString()`,可展示对象字段值,提升调试效率与代码可读性。借助Lombok的`@Data`注解,能自动生成标准化的`toString()`方法,简化开发流程,尤其适合字段较多的场景。合理运用`toString()`,可显著提高开发效率与代码质量。
79 0
|
13天前
|
SQL Java 数据库连接
Java中实现SQL分页的方法
无论何种情况,选择适合自己的,理解了背后的工作原理,并能根据实际需求灵活变通的方式才是最重要的。
35 9
|
1月前
|
存储 Java 开发者
Java 中的 equals 方法:看似简单,实则深藏玄机
本文深入探讨了Java中`equals`方法的设计与实现。默认情况下,`equals`仅比较对象引用是否相同。以`String`类为例,其重写了`equals`方法,通过引用判断、类型检查、长度对比及字符逐一比对,确保内容相等的逻辑。文章还强调了`equals`方法需遵循的五大原则(自反性、对称性等),以及与`hashCode`的关系,避免集合操作中的潜在问题。最后,对比了`instanceof`和`getClass()`在类型判断中的优劣,并总结了正确重写`equals`方法的重要性,帮助开发者提升代码质量。
72 1
|
2月前
|
存储 JSON Java
《从头开始学java,一天一个知识点》之:方法定义与参数传递机制
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 🚀 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。上篇:《输入与输出:Scanner与System类》 | 下篇剧透:《方法重载与可变参数》。
69 25
|
4月前
|
存储 Java 索引
Java快速入门之数组、方法
### Java快速入门之数组与方法简介 #### 一、数组 数组是一种容器,用于存储同种数据类型的多个值。定义数组时需指定数据类型,如`int[]`只能存储整数。数组的初始化分为静态和动态两种: - **静态初始化**:直接指定元素,系统自动计算长度,如`int[] arr = {1, 2, 3};` - **动态初始化**:手动指定长度,系统给定默认值,如`int[] arr = new int[3];` 数组访问通过索引完成,索引从0开始,最大索引为`数组.length - 1`。遍历数组常用`for`循环。常见操作包括求和、找最值、统计特定条件元素等。
|
2月前
|
安全 IDE Java
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
83 1
|
1月前
|
Java
java中一个接口A,以及一个实现它的类B,一个A类型的引用对象作为一个方法的参数,这个参数的类型可以是B的类型吗?
本文探讨了面向对象编程中接口与实现类的关系,以及里氏替换原则(LSP)的应用。通过示例代码展示了如何利用多态性将实现类的对象传递给接口类型的参数,满足LSP的要求。LSP确保子类能无缝替换父类或接口,不改变程序行为。接口定义了行为规范,实现类遵循此规范,从而保证了多态性和代码的可维护性。总结来说,接口与实现类的关系天然符合LSP,体现了多态性的核心思想。
38 0
|
3月前
|
存储 安全 算法
Java容器及其常用方法汇总
Java Collections框架提供了丰富的接口和实现类,用于管理和操作集合数据。
Java容器及其常用方法汇总
|
3月前
|
存储 缓存 Java
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
291 3
java语言后台管理ruoyi后台管理框架-登录提示“无效的会话,或者会话已过期,请重新登录。”-扩展知识数据库中密码加密的方法-问题如何解决-以及如何重置若依后台管理框架admin密码-优雅草卓伊凡
|
2月前
|
运维 Java 程序员
Java中的异常处理方法
本文深入剖析Java异常处理机制,介绍可检查异常、运行时异常和错误的区别与处理方式。通过最佳实践方法,如使用合适的异常类型、声明精确异常、try-with-resources语句块、记录异常信息等,帮助开发者提高代码的可靠性、可读性和可维护性。良好的异常处理能保证程序稳定运行,避免资源泄漏和潜在问题。
101 5

热门文章

最新文章