Java笔记15——二叉树

目录

1. 树型结构(了解)

1.1 概念

节点概念结论

1.3 树的表示形式(了解)

1.4 树的应用 

2. 二叉树(重点)

2.1 概念

2.2 二叉树的基本形态

2.3 两种特殊的二叉树

2.4 二叉树的性质

常见二叉树介绍:

2.5 二叉树的存储 

2.6 二叉树的基本操作

2.6.1 二叉树的遍历

举例:前序遍历 

举例:中序遍历

举例:后续遍历 ——左右根

举例:层序遍历

遍历特性:

 前、中、后排序总代码:

二叉树其他基本操作的实现

2.7 基础面试题

2.8 二叉树的层序遍历

拓展知识Map接口


1. 树型结构(了解)

1.1 概念

为什么会存在二叉树?

树:高效的查找与搜索语义。

当你看到某个场景使用的二叉树,百分之99都是用在查找和搜索。

树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看 起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点

1. 有一个特殊的节点,称为根节点,根节点没有前驱节点(如下图的A就是根节点)

2. 除根节点外,其余节点被分成M(M > 0)个互不相交的集合T1、T2、......、Tm,其中每一个集合 Ti (1 <= i <= m) 又是一棵与树类似的子树。每棵子树的根节点有且只有一个前驱,可以有0个或多个后继

注释:从根节点出发分成的M个子集,彼此之间不能相交,若相交,就不是树结构。且只能有一个父节点

3. 树是递归定义的。

现实生活中,企业中员工的分类其实就是一个"树"结构

 把图倒过来看,也有点像金字塔结构。

但如果是一个线性结构会怎么样?
所有人大家都在一个"目录"中,比如当前公司中一共有300号员工,要找到一个特定的员工最坏情况下得找300次,复杂度:O(n)

 

可如果现在是一个树结构,按照员工的职级进行分类,当前300个员工一共分为五级,搜索一个特定员工只需要5次即可找到。复杂度为:O(logn)——树的高度

300可能看不出有多快,但如果是1亿人呢?树结构只需要10次不到就够了,快了不止一星半点儿

再比如操作系统中OS的文件系统就是基于树结构进行文件管理的

就是电脑硬盘 

 

节点概念结论

1.子树不相交
2.除了根节点没有父节点之外,每个节点有且只有一个父节点。

3.树,边的个数为x,和树中节点的个数为n, 它们之间想关系为:x = n -1 (每个节点都有一个父节点,只有根节点没有父节点)

4.节点和树的"度":

节点的度:一个节点含有的子树的个数称为该节点的度;(说白了就是该节点有多少分支)


树的度:一棵树中,最大的节点的度称为树的度;(最大分支的节点度为树的度)

比如:上图中企业管理图中

 5.叶子结点: 度为0的节点,如上图的:人事、财务、研发等等;都没有子树,都是叶子节点

   非叶子节点: 度不为0的节点,还存在子树,如上图中:副总1、2、3等;都是非叶子节点

6.根节点:没有父节点的结点,树中有且只有一个,上图中:董事长;就是根节点
 

7.节点的层次(高度):从根节点开始计算,根节点为第一层,上图中:总经理为第二层,副总为第三层,人事、研发等为第四层。

这里就还有个概念,树的高度: 上图中当前树中节点层次最大的即为树的高度,4
 

树的以下概念只需了解,在看书时只要知道是什么意思即可:

对应的图:

非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点

兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点

堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点 

节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先

子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙

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

1.3 树的表示形式(了解)

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式,如:双亲表示法, 孩子表示法、孩子兄弟表示法等等。

我们这里就简单的了解其中最常用的孩子兄弟表示法。

 

1.4 树的应用 

文件系统管理(目录和文件)

2. 二叉树(重点)

2.1 概念

一棵二叉树是结点的一个有限集合,该集合或者为空,或者是由一个根节点加上两棵别称为左子树和右子树的二叉 树组成。 

二叉树的特点:

1. 每个结点最多有两棵子树,即二叉树不存在度大于 2 的结点。

2. 二叉树的子树有左右之分,其子树的次序不能颠倒,因此二叉树是有序树。

注释:一个树型结构的最大节点度为N,那么这就是N叉树。

2.2 二叉树的基本形态

 上图给出了几种特殊的二叉树形态,从左往右依次是:空树只有根节点的二叉树节点只有左子树节点只有右 子树节点的左右子树存在,一般二叉树都是由上述基本形态结合而形成的。

2.3 两种特殊的二叉树

1. 满二叉树: 一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果 一个二叉树的层数为K,且结点总数是 2^k  -  1 ,则它就是满二叉树。

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

注释:完全二叉树和满二叉树存在的节点编号必须一一对应。

完全二叉树用在:"堆"-优先级队列

2.4 二叉树的性质

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

2. 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数是 2^k  -  1  (k>=0) 

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

4. 具有n个结点的完全二叉树的深度k为 上取整.

5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i 的结点有:

解析举例:拿上图中的满二叉树举例。

是深度为 4 的满二叉树。全部的节点个数为:1 + 2 + 4 + 8 = 2^4  -  1 = 15个,对应上面的性质2

第三层还有第四层的最大节点个数为: 2^(3 - 1)  和  2^(4 - 1)  ==  4  和  8  对应性质 1

常见二叉树介绍:

满二叉树: 

完全二叉树:

 从视觉上来看,完全二叉树就是满二叉树缺了一个"右一下角"而已。

 完全二叉树和满二叉树存在的节点编号一一对应。(编号顺序是上到下,左到右)

 注释:就算上图第二幅完全二叉树中没有最下面编号为 10的节点,任然还是完全二叉树,因为他满足只缺右下角,存在的节点编号和满二叉树一一对应。

什么样不算完全二叉树?

 这就不是完全二叉树了。

一:还缺了个左下角。

二:本身编号为10的节点在满二叉树中是 11,没有一一对应了。

还有一种:

 该图中少了一个原本编号为 7的节点。虽然看上去还是少了右下角,但一样的,编号跟满二叉树对不上了,没有做到一一对应。

注释:主要是看编号是否对应!!!

完全二叉树官方定义:完全二叉树的节点编号和满二叉树完全一致。

也可以这么理解:完全二叉树只可能最深的一层结点没有拉满没有拉满的这一层,结点都是靠左排列

注释:在完全二叉树中不存在有右子树却没有左子树的情况。不然就不是完全二叉树

再深度理解一下:在完全二叉树中,若存在度为1的节点,这个节点必然只有左树没有右树而且这个节点有且只有1个! !

如图:有且只有一个。

 关于完全二叉树编号的问题:

从1开始编号

 从0开始编号

二分搜索树:节点的之间有一个大小关系

 平衡二叉树:该树中任意一个节点的左右子树高度差 <=  1

2.5 二叉树的存储 

二叉树的存储结构分为:顺序存储和类似于链表的链式存储。

顺序存储下次讲。

二叉树的链式存储是通过一个一个的节点引用起来的,常见的表示方式有二叉和三叉表示方式,具体如下:

 示例代码:

// 孩子表示法
class Node {
 int val; // 数据域
 Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
 Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
}
// 孩子双亲表示法
class Node {
 int val; // 数据域
 Node left; // 左孩子的引用,常常代表左孩子为根的整棵左子树
 Node right; // 右孩子的引用,常常代表右孩子为根的整棵右子树
 Node parent; // 当前节点的根节点
}

孩子双亲表示法后序在平衡树位置再说,这次采用孩子表示法来构建二叉树。

2.6 二叉树的基本操作

2.6.1 二叉树的遍历

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

遍历:按照一定的顺序访问这个集合的所有元素,做到不重复不遗漏
 

 二叉树的遍历问题:所有二叉树的基础操作,其实所有二叉树问题的解决思路都是遍历问题的衍生。

在遍历二叉树时,如果没有进行某种约定,每个人都按照自己的方式遍历,得出的结果就比较混乱,如果按照某种 规则进行约定,则每个人对于同一棵树的遍历结果肯定是相同的。

如果N代表根节点,L代表根节点的左子树,R代 表根节点的右子树,则根据遍历根节点的先后次序有以下遍历方式:

 上面三种叫:深度优先遍历 dfs

还有一种,广度优先遍历 :层序遍历(BFS),将二叉树一层层的访问。

对于二叉树来说,一共有四种遍历方式。

上面的三种深度优先遍历是以递归的形式进行的,比如访问左子树时,是把左子树全部访问完毕了才会去范围右子树。

举例:前序遍历 

前序遍历总结三个字:根、左、右。有根先执行根等等。。。

打印结果:ABDEC

 线条执行顺序:

 相当于递归思想一样,一个节点没走到底,就一直往下走,其他都是停着。等走到不能再走时,再一个个的返回。

一般来说,深度优先遍历借助 "  栈  " 观察; 广度优先遍历借助 "  队列  " 观察

比如说栈:一个节点的根入栈,当一个根节点的左子树和右子树都执行完毕时出栈。

举例:中序遍历

注释:当根节点第二次走到时才能"访问"

 当我们第一次走到A的时候,因为是中序遍历,不能马上打印这个A,先压入栈中。

 然后递归范围左子树B,B是左子树的根节点,也是第一次访问,不能马上打印,先压入栈中。

 再去访问以B为根节点的左子树D,D也一样是第一次出现不能打印,压入栈。

 当以D为根节点去访问左子树时,发现为空返回。回到D后此时D是第二次访问了,可以打印了D

 然后访问以D为根节点的右子树,访问为空就返回。当左右子树都访问完了以后,把D弹出栈,然   后返回到B节点。此时B是第二次访问了就打印B

 以B为根节点的左子树已经执行完了就去执行右子树。E,E下面都空返回打印E。

后面的都以此类推~结果:DBEAC

举例:后续遍历 ——左右根

注释:当第三次走到根节点才能访问。

 跟中序遍历差不多,不过是第三次才能被打印。打印:DEBCA

 注释:前面三种怎么走?看跟在第几位就需要访问几次才能被打印。

法则:按这个方法画图,无论什么二叉树都不会错。

1. 前序遍历:根,左,右 。 在第一位,所以第一次访问就可以打印。

2. 中序遍历:左,根,右 。 在第二位,所以第二次访问才可以打印。

3. 前序遍历:左,右 ,根。 在第三位,所以第三次访问才可以打印。

举例:层序遍历

最简单:从上到下,从左到右。一层走完再去走下一层。

 打印顺序:ABCDE

思考题:按上面讲的方法,写出下面二叉树的四种遍历顺序。

 前序遍历:ABDEGHCF

中序遍历:DBGHEACF

后续遍历:DHGEBFCA

层序遍历:ABCDEFGH

遍历特性:

1. 前序遍历的第一个节点一定是根节点。

2. 中序遍历的结果中:左子树在该根节点的左边,右子树在该根节点的右边。
如:看上面中序的结果,A节点的左边DBGHE都是左子树,CF是右子树。

当以B为根节点情况也是同样 左 E 右 GHE

3. 后续遍历的结果一翻转:reverse

ACFBEGHD——根右左

相当于前序遍历的镜像,后续遍历逆序的结果就是先走右子树了。

后序遍历的最后—个结果为根节点
 

代码实现:

1. 先创建节点类

//二叉树的节点
class TreeNode<E> {
    //当前节点的值
    E val;
    //左子树的跟
    TreeNode<E> left;
    //右子树的跟
    TreeNode<E> right;
    //构造方法
    public TreeNode(E val) {
        this.val = val;
    }
}

2. 前序遍历的实现

public class MyBinTree<E> {
    public TreeNode<Character> root;//根节点

    //建立一个测试二叉树
    public void build(){
        //假设有8个节点
        TreeNode<Character> node1 = new TreeNode<>('A');
        TreeNode<Character> node2 = new TreeNode<>('B');
        TreeNode<Character> node3 = new TreeNode<>('C');
        TreeNode<Character> node4 = new TreeNode<>('D');
        TreeNode<Character> node5 = new TreeNode<>('E');
        TreeNode<Character> node6 = new TreeNode<>('F');
        TreeNode<Character> node7 = new TreeNode<>('G');
        TreeNode<Character> node8 = new TreeNode<>('H');
        //节点的连接

        //A
        node1.left = node2;
        node1.right = node3;
        //B
        node2.left = node4;
        node2.right = node5;
        //E
        node5.left = node7;
        //G
        node7.right = node8;
        //C
        node3.right = node6;

        //连接根节点
        root = node1;
    }

    /**
     * 传入一颗二叉树的根节点root。就能按照前序遍历根左右的方式进行输出
     */
    public void preOrder(TreeNode root){
        //终止条件
        if(root == null){
            return;
        }
        //我们只负责打印跟
        System.out.print(root.val + " ");
        //左子树的输出交给子函数
        preOrder(root.left);
        //右子树的输出交给子函数
        preOrder(root.right);
    }

}

测试:看看我们写的前序遍历结果是否正确

//二叉树测试模块
public class TreeTest {
    public static void main(String[] args) {
        MyBinTree binTree = new MyBinTree();
        binTree.build();
        System.out.print("前序遍历结果为:");
        binTree.preOrder(binTree.root);
    }
}

运行结果:正确

3.实现后续遍历的方法

跟前序的实现差不多,输出的位置不一样。

    /**
     * 传入一颗二叉树的根节点root。就能按照中序遍历左根右的方式进行输出
     */
    public void inOrder(TreeNode root){
        //终止条件
        if(root == null){
            return;
        }
        //左子树的输出交给子函数
        inOrder(root.left);

        //我们只负责打印跟
        System.out.print(root.val + " ");

        //右子树的输出交给子函数
        inOrder(root.right);

    }

 测试:

//二叉树测试模块
public class TreeTest {
    public static void main(String[] args) {
        //前序
//        MyBinTree binTree = new MyBinTree();
//        binTree.build();
//        System.out.print("前序遍历结果为:");
//        binTree.preOrder(binTree.root);

        //中序
        MyBinTree binTree1 = new MyBinTree();
        binTree1.build();
        System.out.print("中序遍历结果为:");
        binTree1.inOrder(binTree1.root);
    }
}

运行结果:DBGHEACF

    /**
     * 传入一颗二叉树的根节点root。就能按照中序遍历左根右的方式进行输出
     */
    public void inOrder(TreeNode root){
        //终止条件
        if(root == null){
            return;
        }
        //左子树的输出交给子函数
        inOrder(root.left);

        //我们只负责打印跟
        System.out.print(root.val + " ");

        //右子树的输出交给子函数
        inOrder(root.right);

    }

4. 后续遍历也差不多

    /**
     * 传入一颗二叉树的根节点root。就能按照后序遍历左右根的方式进行输出
     * @param root
     */
    public void postOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        // 先打印左子树,交给子函数
        postOrder(root.left);

        // 再打印右子树
        postOrder(root.right);

        // 最后打印根
        System.out.print(root.val + " ");
    }

测试

        //后续
        MyBinTree binTree2 = new MyBinTree();
        binTree2.build();
        System.out.print("后序遍历结果为:");
        binTree2.postOrder(binTree2.root);

运行结果:DHGEBFCA

 前、中、后排序总代码:

package bintree;

/**
 *  二叉树的基本操作
 */

//二叉树的节点
class TreeNode<E> {
    //当前节点的值
    E val;
    //左子树的跟
    TreeNode<E> left;
    //右子树的跟
    TreeNode<E> right;
    //构造方法
    public TreeNode(E val) {
        this.val = val;
    }
}


public class MyBinTree<E> {
    public TreeNode<Character> root;//根节点

    //建立一个测试二叉树
    public void build(){
        //假设有8个节点
        TreeNode<Character> node1 = new TreeNode<>('A');
        TreeNode<Character> node2 = new TreeNode<>('B');
        TreeNode<Character> node3 = new TreeNode<>('C');
        TreeNode<Character> node4 = new TreeNode<>('D');
        TreeNode<Character> node5 = new TreeNode<>('E');
        TreeNode<Character> node6 = new TreeNode<>('F');
        TreeNode<Character> node7 = new TreeNode<>('G');
        TreeNode<Character> node8 = new TreeNode<>('H');
        //节点的连接

        //A
        node1.left = node2;
        node1.right = node3;
        //B
        node2.left = node4;
        node2.right = node5;
        //E
        node5.left = node7;
        //G
        node7.right = node8;
        //C
        node3.right = node6;

        //连接根节点
        root = node1;
    }

    /**
     * 传入一颗二叉树的根节点root。就能按照前序遍历根左右的方式进行输出
     */
    public void preOrder(TreeNode root){
        //终止条件
        if(root == null){
            return;
        }
        //我们只负责打印跟
        System.out.print(root.val + " ");
        //左子树的输出交给子函数
        preOrder(root.left);
        //右子树的输出交给子函数
        preOrder(root.right);
    }

    /**
     * 传入一颗二叉树的根节点root。就能按照中序遍历左根右的方式进行输出
     */
    public void inOrder(TreeNode root){
        //终止条件
        if(root == null){
            return;
        }
        //左子树的输出交给子函数
        inOrder(root.left);

        //我们只负责打印跟
        System.out.print(root.val + " ");

        //右子树的输出交给子函数
        inOrder(root.right);

    }

    /**
     * 传入一颗二叉树的根节点root。就能按照后序遍历左右根的方式进行输出
     * @param root
     */
    public void postOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        // 先打印左子树,交给子函数
        postOrder(root.left);
        // 再打印右子树
        postOrder(root.right);
        // 最后打印根
        System.out.print(root.val + " ");
    }

}

测试总代码:

package bintree;
//二叉树测试模块
public class TreeTest {
    public static void main(String[] args) {
        //前序
        MyBinTree binTree = new MyBinTree();
        binTree.build();
        System.out.print("前序遍历结果为:");
        binTree.preOrder(binTree.root);
        System.out.println();
        System.out.print("中序遍历结果为:");
        binTree.inOrder(binTree.root);
        System.out.println();
        System.out.print("后序遍历结果为:");
        binTree.postOrder(binTree.root);



    }
}

二叉树其他基本操作的实现

1.写一个方法统计当前二叉树中的节点个数
 

    /**
     * 传入一颗以root为根的二叉树,就能求出节点个数为多少
     */
    public int getNodes(TreeNode root){
        //1.边界
        if(root == null){
            return 0;
        }
        //此时二叉树不为空,root != null,根节点存在,所以至少存在 1
        //总节点个数 = 根节点 + 左子树的所用节点 + 右子树的所有节点
        return 1 + getNodes(root.left) + getNodes(root.right);

    }

⒉.统计一颗二叉树中叶子结点的个数

    /**
     * 传入一颗以root为根的树,就能求出叶子节点的个数
     */
    public int getLeafNodes(TreeNode root){
        //1. 边界
        if(root == null){
            return 0;//此时节点都为空了,哪来什么子树
        }
        //到了这里说明该节点不为空,看看它的子树
        //2.判断是否为叶子节点
        if(root.left == null && root.right == null){
            return 1;
        }
        //3.到了这里说明该节点不是叶子节点,且有子树。
        //看看它的子树里面是否存在叶子节点。
        //总叶子节点个数 = 左树中的叶子节点个数 + 右树中的叶子节点个数
        return getLeafNodes(root.left) + getLeafNodes(root.right);
    }

3.求出第k层的节点个数 k <= height(root)

    /**
     * 传入一颗以root为根的二叉树,就能求出第k层的节点个数 k <= height(root)
     */
    public int getKLevelNodes(TreeNode root,int k){
        //1.边界
        if(root == null || k <= 0){
            return 0;
        }
        //当k == 1时,说明求的是输出的根节点这层
        if(k == 1){
            //第一层为根节点,肯定只有一个。
            return 1;
        }
        //当k != 1时,说明改层不是要求的层次,交给子方法去求
        // 求以root为根的第k层结点个数 =
        // 以root.left为根的第k - 1层结点个数 + 以root.right为根的第k - 1层结点个数
        return getKLevelNodes(root.left, k - 1) + getKLevelNodes(root.right, k - 1);
    }

4.求二叉树的高度

    /**
     * 传入一颗以root为根的二叉树,就能求出该树的高度是多少
     */
    public int height(TreeNode root){
        //1.边界
        if(root == null){
            return 0;
        }
//        //2.该节点下面没有子树了,这就是它的最高高度了。
//        if(root.left == null && root.right == null){
//            return 1;
//        }
//        int left = height(root.left);//左子树里面最深的高度
//        int right = height(root.right);//右子树里面最深的高度
//        return 1 + (left > right ? left : right);
        
        //也可以这么写:
        // 1就是当前树根所在的第一层
        // 树高 = 根节点这一层 + max(左子树的树高,右子树的树高)
        return 1 + Math.max(height(root.left), height(root.right));
    }

5.判断—颗二叉树中是否包含指定的值val

    /**
     *  判断以root为根的二叉树中是否包含指定值val
     */
    public boolean contains(TreeNode<E> root,E val){
        if (root == null) {
            return false;
        }
        if (root.val.equals(val)) {
            return true;
        }
        // 继续在左子树和右子树中寻找是否包含指定值
        return contains(root.left,val) || contains(root.right,val);
    }

2.7 基础面试题

注释:在leetcode和牛客网上,若定义了全局变量,千万不要加static! ! ! !

假设此时有100个人都在做这个题
每个人其实就是一个该类的对象,若你定义的是static数组,这个数组就被这100个人共享,别人把这个数组修改对你是可见。

144. 二叉树的前序遍历 - 力扣(LeetCode)

94. 二叉树的中序遍历 - 力扣(LeetCode)

145. 二叉树的后序遍历 - 力扣(LeetCode)

100. 相同的树 - 力扣(LeetCode)

572. 另一棵树的子树 - 力扣(LeetCode)

104. 二叉树的最大深度 - 力扣(LeetCode)

110. 平衡二叉树 - 力扣(LeetCode)

Loading Question... - 力扣(LeetCode)101. 对称二叉树 - 力扣(LeetCode)Loading Question... - 力扣(LeetCode)

2.8 二叉树的层序遍历

因为二叉树天然的"递归"结构,因此二叉树的递归代码结合语义可以很方便的写出。

反而二叉树的迭代写法就比递归难一些。


二叉树的层序遍历―借助队列实现

为什么要用队列?因为要利用 先入先出的特性。

执行顺序:

1.树根先入队列,队列:A

然后队列里面的第一位数我们这简称为:根

2.如果根的左节点存在,就让根的左节点入栈

3.如果根的右节点存在,就让根的右节点入栈

队列:A、B、C

4.第一位数左右节点指向完毕就弹出。

队列:B、C

5. 开始执行队列的第一位数,B现在作为根节点。

执行左右节点入队列,有就入。

队列:B、C、D、E

6. B弹出

队列:C、D、E

后面的步骤都一样,直到队列为空结束

注释:这里的弹出就表示打印这个节点值

结论:

1. 每当遍历—层结点结束,队列中恰好存储了下一层要遍历的结点。


2. 当整个队列为空,二叉树的层序遍历就结束了~

代码实现:

    /**
     * 借住队列,实现二叉树的层序遍历
     */
    public void levelOrder(TreeNode<E> root){
        //创建一个队列,用双端队列
        Deque<TreeNode<E>> queue = new LinkedList<>();
        queue.offer(root);
        //循环的终止条件就是队列为空
        while(!queue.isEmpty()){
            //当前队列里面的节点个数,就是当前需要执行的循环次数。
            int n = queue.size();
            for (int i = 0; i < n; i++) {

                TreeNode<E> node = queue.poll();//出队列
                System.out.print(node.val + " ");//打印弹出的节点

                if(node.left != null){
                    queue.offer(node.left);
                }
                if(node.right != null){
                    queue.offer(node.right);
                }

            }

        }

    }

有了层序遍历这个方法就可以改写很多我们之前写的方法,不再使用递归去写。

举例:用层序遍历统计节点个数

    /**
     * 使用层序遍历统计二叉树的节点个数
     */
    public int getNodesNonRecursion(TreeNode root){
        //1.边界
        if(root == null){
            return 0;
        }
        int size = 0;
        Deque<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while(!queue.isEmpty()){
            TreeNode cur = queue.poll();
            //此处的遍历指的是讲节点个数统计
            size ++;
            if(cur.left != null){
                queue.offer(cur.left);
            }
            if(cur.right != null){
                queue.offer(cur.right);
            }
        }

        return size;
    }

拓展知识Map接口

Map接口:存储一个键值对数据

key = value

定义方法:

Map< K , V > map = new HashMap<>( );

//常用方法,put方法:录入

map.put(key,value);

containsKey方法:判断key值是否存在

containsValue方法:判断value值是否存在

现实生活中,名字经常重复,用身份证号对应名字。

map(身份证号,名字);

代码示例:

import java.util.HashMap;
import java.util.Map;

//Map接口——存储一个键值对数据
public class MapTest {
    public static void main(String[] args) {
        //key不重复,value可以重复
        Map<Integer,Integer> map = new HashMap<>();
        //录入
        map.put(1,66);
        map.put(2,99);
        //value可以重复,用一个key会得到一个value值
        //相当于key是一个编号,一个编号对应一个物品,X,Y坐标轴一个意思
        map.put(3,66);
        System.out.println(map.containsKey(3));//是否存在编号3
        System.out.println(map.containsValue(66));//是否存在value值66的值
        System.out.println(map.containsValue(77));//是否存在value值77的值
    }
}

 

1. 什么是二叉树二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值