学习二叉树 这一篇就够了 - java

x33g5p2x  于2022-02-07 转载在 Java  
字(21.4k)|赞(0)|评价(0)|浏览(324)

什么是树?

树形结构的概念

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

它具有以下特点:
1、有一个特殊的结点,称为根结点,根节点没有前驱结点

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

3、 树是递归定义的。(这个等到 具体讲二叉树的时候会讲到的)
注意:树形结构中,子树之间不能有交集,否则就不是树形结构。

重要概念

结点的度:一个结点含有子树的个数;如上图:A的度为6。

树的度:一棵树中,所有结点度的最大值称为树的度;如上图:树的度为6。

叶子结点或终端结点:度为0的节点成为叶节点;如上图:B、C、H、I、P、Q、K、L、M、N都是叶子结点。

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点/双亲节点;如上图:A 是 B 的父结点。还有很多就不说了。

孩子结点或子结点一个结点 含有的子树的根结点 称为 该节点 的 子结点;如上图: B 是 A 的孩子结点

根结点:一棵树中,没有双亲结点的结点;如上图:A

结点的层次:从根开始定义起,根为第一层,根的子结点为第2层,依次类推。

树的高度或深度:树中最大深度 ,即为高度;如上图:树的高度为 4,最大深度也为 4。但两者是不同的定西。
只有:当深度最大时, 深度才能说是高度。

非终端结点或分支结点: 度不为0的节点;如上图:D E F G J 都是分支结点。【简单来说:除了叶子结点【度为0的结点】,其它结点都是分支结点/非终端结点】

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

堂兄弟结点:双亲/父 在同一层的结点互为堂兄弟; 如上图:H I 互为堂兄弟结点。

结点的祖先:从该节点出发,可以经过所有分支上的节点;如上图:A 是 所有节点的祖先。

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

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

树的表示形式

树结构相对线性表就比较复杂了,要存储表示起来就比较麻烦了,实际中树有很多种表示方式.
如:双亲表示法、孩子表示法、孩子双亲表示法、孩子兄弟表示法等等。
我们这里就简单的了解其中最常用的孩子兄弟表示法。

这里,我们在额外的讲一下 双亲表示法,就是 孩子兄弟法,加了一个存储空间,用来存储自身的父结点

树的应用

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

试想一下:我们的电脑常有空文件夹,点进去,就没有东西点了。对不对?那这空文件夹是不是就可以看作是叶子结点!【没有子结点,或者“度”为零嘛。】

再来举一个例子:

二叉树

概念

一棵二叉树是结点的一个有限集合,该集合:
1、可能为 空
2、可能是由一个根结点加上两棵别称为 左子树 和 右子树 的 二叉树组成

二叉树 跟我们前面讲的树的区别就在于:二叉树 的 每个结点,最多只能有 两个 “孩子”/子树.。最少 零个。
也就是说:一棵树,如果是二叉树,那么它的每棵子树都是 二叉树【都有左子树 和 右子树】。

总结

  1. 二叉树不存在度大于2 的结点
    2.二叉树的子树有左右之分,次序不能颠倒,因此二叉树为有序树。
    注意:对于任意的二叉树都是由以下几种情况复合而成的

上图的这四棵树都是二叉树

两种特殊的二叉树

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


2、完全二叉树:完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树引申出来的。对于深度为K的,有n个节点的二叉树,当其每一个结点都与深度为k的满二叉树中编号从 0 值 n -1 的结点 一一对应时称之为完全二叉树。值得注意的是 满二叉树 是一种特殊的 完全二叉树
【简单来说: 给二叉树编号,根结点为0,然后下一层次 从左往右,依次编号。中间不能有间断,依次类推】

二叉树性质

性质1、 若规定根结点的层数为1,则一棵非空二叉树的第i层上,最多有 2的 (i - 1)次方。【这个我们在前面推导满二叉树的时候,已经出来了 2的(k-1)次方】
性质2、若规定只有根结点的二叉树的深度为1,则深度为k的二叉树的最大结点数是 2的K次方减一(k>=0)。【就是满二叉树】
性质3、对于任何一棵二叉树,如果其 叶结点个数 为 n0,度为2的非叶结点个数为 n2,则有 n0 = n2 + 1。【也就是说:叶结点的个数 总是比 非叶结点的个数多一个】


得出结论:任何一棵二叉树的叶子结点 比 度为2的节点 多一个。

来看三个练习题

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

再来看个练习题

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

若 i > 0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
若2i+1<n,左孩子序号:2i+1,否则无左孩子
若2i+2<n,右孩子序号:2i+2,否则无右孩子

二叉树存储

二叉树的存储结构分为:顺序存储类似于链表的链式存储
这里,我们讲链式存储。
二叉树的链式存储是通过一个一个的节点引用起来的。常见的表示方式有二叉 和 三叉表示方式。
【二叉 : 孩子表示法;三叉 :孩子双亲表示法】

模拟实现二叉树

提前说明:二叉树的构建是一个非常复杂的过程,因为目前作者对二叉树的理解,还不是很深。所以,我们先会创建一个二叉树,但是这种创建方式,很LOW, 只是为了应付前期使用,比较简单,不是正确的常用创建方式。

准备工作

构造二叉树节点 - (孩子表示法)

构建一棵这样的二叉树

debug【调试效果图】

二叉树最重要的功能 - 遍历二叉树 - (一级标题 更显重要。)

学习二叉树结构,最简单的方式就是遍历。所谓遍历(Traversal)是指沿着某条搜索路线,依次对树中每个结
点均做一次且仅做一次访问。访问结点所做的操作依赖于具体的应用问题【打印节点值,求节点个数等】。所有的二叉树相关的题目,基本上都是需要通过某种遍历去解题的。

1. NLR:前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点—>根的左子树—>根的右子树

2. LNR:中序遍历(Inorder Traversal)——根的左子树—>根节点—>根的右子树

它的遍历顺序 就是 左子树 》》 根结点 》》 右子树

3. LRN:后序遍历(Postorder Traversal)——根的左子树—>根的右子树—>根节点

遍历顺序: 左子树 》》 右子树 》》 根结点

小结

前中后,无论是哪一种遍历,都是按照下面图的路线,来走的,只是打印的顺序不同而已,

练习

写出下面二叉树的 前中后排序的 序列

前序遍历:A B D E H C F G
中序遍历:D B H E A F C G
后序遍历:D H E B F G C A

程序实现 - 前中后序遍历

接着模拟实现二叉树,完成其遍历功能

前序遍历

拓展

二叉树的题 天生就是用递归来写的。【90%】
剩余 10%,使用非递归来解决问题。【只针对部分题】
其实所有的递归 都是 跟栈有关系的。

中序遍历

与前序同理,就是打印顺序不同.

// 中序序遍历
    public void inorderTraversal(BTNode root){
        if(root == null){
            return;
        }
        inorderTraversal(root.left);
        System.out.print(root.val + " ");
        inorderTraversal(root.right);

    }

后序遍历

与前序同理,就是打印顺序不同.

// 后序遍历
    public void postorderTraversal(BTNode root){
        if(root == null){
            return;
        }
        postorderTraversal(root.left);
        postorderTraversal(root.right);
        System.out.print(root.val + " ");
    }

实战题 - LeetCode - 144. 二叉树的前序遍历 - 遍历思维

这题,你可以直接将我们刚才的程序,复制粘贴。
唯一需要注意的就是

不过也很好解决。new一个 List 的嘛。反正它只用来存储前序遍历的结果序列。

其次,就是把输出语句 换成 add 添加 语句,将遍历的结果存入

代码如下
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<Integer> list;
    public List<Integer> preorderTraversal(TreeNode root) {
        list =  new ArrayList<>();
        preorder(root);
        return list;
    }
    public void preorder(TreeNode root){
        if(root == null){
            return;
        }
        list.add(root.val);
        preorder(root.left);
        preorder(root.right);
    }
}

实战题 - LeetCode - 94. 二叉树的中序遍历 - 遍历思维

做法 完全一样,改变一下,add 添加数据的代码位置就行了

代码如下:
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<Integer> list;
    public List<Integer> inorderTraversal(TreeNode root) {
        list = new ArrayList<>();
        inorder(root);
        return list;
    }
    public void inorder(TreeNode root){
        if(root == null){
            return;
        }
        inorder(root.left);
        list.add(root.val);
        inorder(root.right);
    }
}

实战题 - LeetCode - 145. 二叉树的后序遍历- 遍历思维

我就直接上代码。。。。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    List<Integer> list;
    public List<Integer> postorderTraversal(TreeNode root) {
        list = new ArrayList<>();
        postorder(root);
        return list;
    }
    public void postorder(TreeNode root){
        if(root == null){
            return;
        }
        postorder(root.left);
        postorder(root.right);
        list.add(root.val);
    }
}

拓展 : 实战题 - LeetCode - 144. 二叉树的前序遍历 - 子问题思路

将二叉树 整体分为 根结点,左子树 和 右子树 三部分,将其按照前序的遍历顺序进行添加。

代码如下
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        if(root == null){
            return list;
        }
        list.add(root.val);
        List<Integer> treeLeft = preorderTraversal(root.left);
        list.addAll(treeLeft);
        
        List<Integer> treeRight = preorderTraversal(root.right);
        list.addAll(treeRight);
        return list;
    }
}

代码分析

4. 层序遍历

除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在
层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层
上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历

练习题

现在开始实现二叉树所具有的基本功能

获取二叉树的节点个数

获取叶子结点个数

获取第K层节点的个数 - 子问题思路

获取二叉树的高度 - 子问题思路

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

我们都知道在数组当中 查找一个数据:遍历数组,依次查找。
如果是链表中,是不是也是需要从头节点开始遍历比较,确认是不是存在的。
而现在是二叉树,是不是也需要遍历二叉树紫红的节点,确定某一个节点的值,是不是我们要找的。
最简单就是运用前序遍历来实现。先判断根结点值是否与 value值相等,其次 左子树,最后是右子树。

// 检测值为value的元素是否存在
    public BTNode find(BTNode root,char value){
        if(root == null){
            return null;
        }
        if(root.val == value){// 判断根结点是不是 指定的数据
            return  root;
        }
        BTNode node1 = find(root.left,value);// 查找 左子树 是有存在 指定value值的节点【找到了,返回其引用,否,则返回null】
        BTNode node2 = find(root.right,value);// 查找 右子树 是有存在 指定value值的节点【找到了,返回其引用,否,则返回null】
        return node1 == null ? node2:node1;// 如果 node1 为 null,说明左子树没有指定value值的节点,返回 node2【右子树的查找的结果】
        // 如果 node2 也没有,也没有关系,反正都是返回null,node2找到了更好。返回其节点的引用
    }

判断一棵树是不是完全二叉树

拓展: 队列中 入队为元素为 null,队列也是有大小的。

与面试相关的二叉树OJ题型

LeetCode - 100. 相同的树

题目解析

注意我框选的题干部分【如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。】 它的意思: 两棵二叉树的节点位置 与 节点值 都必须一模一样。 两个条件中,只要有一个条件不符合。那么,这棵二叉树就不说是相同的树。

解题思维

利用遍历思想 去完成这道题:就是 使用同一种遍历方式,来遍历两棵二叉树节点,如果两棵树的节点值,有一组不相等就返回false。
但是!我们需要注意几个特殊情况!
情况1:
两棵二叉树为 空树【两棵二叉树的根结点为 null】

情况2:
一棵二叉树为null,另一棵不为null。

代码如下

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null){// 两棵二叉树都为空树
            return true;
        } 
        // 两棵儿茶素中,有一颗树为空树
        if( (p != null && q == null) || (p == null && q != null) ){
            return false;
        }
        // 判断 两棵二叉树中,两个相对应的位置节点val值是否相同【宏观:判断根结点 是否相同】
        if(p.val != q.val){
            return false;
        }
        // 宏观:
        // 走到这里 说明两棵二叉树的头节点相同。接着,就是遍历 两棵二叉树的 左右子树。
        // 必须 两棵二叉树的左右子树都相等【true】。再结合根结点。【true】
        // 这两棵二叉树才能算是相同的树
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right); 
    }
}

LeetCode - 572. 另一棵树的子树

题目解析

简单来说: 在 根结点为 root 的 二叉树中,寻找有没有 一棵 与 根结点为subRoot的二叉树 相同的树。

解题思维

既然是寻找相同的树,那么,肯定是需要一个方法来判断 相同的树。【上一题的代码,就可以直接复制过来,作为一个判断相同树的方法来使用】

subRoot 为 root 的子树,无非就是以下几种情况:
1、root 与 subRoot 是一棵相同的树

2、 subRoot 是 root 的 左子树 ,或者说 是 左子树的一部分,

3、 subRoot 是 root 的 右子树 ,或者说 是 右子树的一部分,


注意:以上这些操作,都可以交由我们的判断相同的方法来操作。但这不意味着我们可以什么都不用做!

代码如下

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if(p == null && q == null){// 两棵二叉树都为空树
            return true;
        } 
        // 两棵儿茶素中,有一颗树为空树
        if( (p != null && q == null) || (p == null && q != null) ){
            return false;
        }
        // 判断 两棵二叉树中,两个相对应的位置节点val值是否相同【宏观:判断根结点 是否相同】
        if(p.val != q.val){
            return false;
        }
        // 宏观:
        // 走到这里 说明两棵二叉树的头节点相同。接着,就是遍历 两棵二叉树的 左右子树。
        // 必须 两棵二叉树的左右子树都相等【true】。再结合根结点。【true】
        // 这两棵二叉树才能算是相同的树
        return isSameTree(p.left,q.left) && isSameTree(p.right,q.right); 
    }

    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if(root == null){
            return subRoot!=null ? false:true;
        }
        // 判断 是否是同一棵树
        if(isSameTree(root,subRoot)){
            return true;
        }
        // 判断 subRoot是否 是 root的左子树 或者 是左子树的一部分
        if(isSubtree(root.left,subRoot)){
            return true;
        }
        // 判断 subRoot是否 是 root的右子树 或者 是右子树的一部分
        if(isSubtree(root.right,subRoot)){
            return true;
        }
        return false;
    }

}

LeetCode - 104. 二叉树的最大深度

这题就是我们前面实现的 求二叉树高度功能。【高度 等价于 二叉树的最大深度】
这里我就直接上代码了。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }
        return  Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
    }
}

LeetCode - 110. 平衡二叉树

题目解析

题目的意思: 让我们去计算 这棵二叉树的左右子树的高度。其高度差不超过 1。
唯一需要注意的是:平衡二叉树 不仅仅是 左右子树 是这么个情况,还需要把左右子树 看作一个 二叉树,而且也必须是一个平衡二叉树。
也就是说:平衡二叉树 的 左右子树高度差 不超过 1,且如果将左右子树看作 是 一棵单独二叉树 ,也是一棵平衡二叉树。

代码如下

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
 时间复杂度 O(N)
 空间复杂度 O(log2N)
class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null){
            return true;
        }
        int leftHeight = getHeight(root.left);// 获取 左子树高度
        int rightHeight = getHeight(root.right);// 获取 右子树高度
        // 判断 左右子树的高度差不能超过 1。另外,左右子树自身  也要满足这个条件。
        return Math.abs(leftHeight - rightHeight) <= 1 && isBalanced(root.left) && isBalanced(root.right);  

    }
    public int getHeight(TreeNode root){// 计算根结点为“root”的二叉树高度
        if(root == null){
            return 0;
        }
        return Math.max(getHeight(root.left),getHeight(root.right)) + 1;
    }
}

进阶

代码如下
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root == null){
            return true;
        }
        return getHeight(root)> -1;
    }
    public int getHeight(TreeNode root){
        if(root == null){
            return 0;
        }
        int L = getHeight(root.left);// 左子树高度
        int R = getHeight(root.right);// 右子树高度
        if(L>=0 && R>=0 && Math.abs(L-R)<=1){
            return Math.max(L,R) + 1;
        }else{// 说明不平衡
            return -1;
        }
    }
}

LeetCode - 101. 对称二叉树

题目解析

简单来说:以根结点为分界线,将二叉树的左右子树分割开来,并以此线 作为对称轴。

对称简单来说:将这上图看作一张纸,沿着 对称轴 折叠,你会发现 左右子树的节点完美重合【位置和值都是】

解题思维

简单来说:以二叉树的根结点 1 为 分割线, 去判断 左右子树 节点的值是否对称。
对称图如下:

但是有几个特殊情况需要注意!
1、二叉树为空树,根结点为null。【空树也是树,也算对称树】

2、二叉树只有一个根结点,也算对称二叉树【左右两个空子树】

3、 二叉树的左右子树中:有一个为空子树【非对称子树】

代码如下:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        // 二叉树为 空树
        if(root == null){
            return true;
        }
        return isSymmetricChild(root.left,root.right);
    }
    public boolean isSymmetricChild(TreeNode L,TreeNode R){
        // 树的根结点 左右子树为null
        if(L == null && R == null){
            return true;
        }
        // 二叉树的左右子树有一棵子树为空树
        if( (L == null && R != null) || (L != null && R == null) ){
            return false;
        }
        // 继续判断:两棵树中 对称节点的值【左对右,右对左】
        if(L.val == R.val){
            return isSymmetricChild(L.left,R.right) &&isSymmetricChild(L.right,R.left);
        }
        return false;
    }
}

牛客题霸 - KY11 二叉树遍历

题目解析

简单来说:根据所给出 二叉树 前序遍历序列,构造出一棵二叉树。对其进行 中序遍历,输出结果。

解题思维

首先,我们先根据 它所给的 前序遍历序列 和 关键条件【# 代表 空树】 ,根据示例的 数据,来 推导构造出一棵二叉树

难点:是推导出来了,也画出来了,但是代码具体如何实现?
答案:既然 题目 给的是前序遍历的结果,我们也需要用前序遍历的方式来构建。

解题步骤

程序框架

creationTree 方法 【构造二叉树】

思路: 创建一个静态变量 i ,用来遍历字符串,辅助我们去构造 一个 二叉树。
不要抱有太大的异或,你就跟着我的思路一步步来。稳得很!

因为我们不知道这个字符串具体输入什么样的数据。所以重点就在于判断!
判断 我们拿到的字符串数据。

代码 如下

import java.util.*;
// 树节点
class TreeNode{
    public char val;
    public TreeNode left;
    public TreeNode right;
    public TreeNode(char val){
        this.val = val;
    }
}
    
public class Main{
    public static void main(String[] args){
        // 循环输入
        Scanner sc = new Scanner(System.in);
        while(sc.hasNextLine()){
            String  str = sc.nextLine();//读取 字符串
            TreeNode root = creationTree(str);// 构造二叉树返回其根结点
            inorderTraversal(root);// 中序遍历打印
        }
        sc.close();
    }
    // 构造二叉树
    public static int i = 0;
    public static TreeNode creationTree(String str){
       TreeNode  root = null;// 先定义 一个为null
        char ch = str.charAt(i);// 获取下标为 i  的 字符 数据
        if( ch != '#'){// ch 为字符数据时
            root  =  new  TreeNode(ch);// root 根据 ch 去创建一个节点
            i++;// 为下一次获取 字符 数据,做准备。
            root.left = creationTree(str);// 宏观:将 左子树接回根结点
            root.right = creationTree(str);// 宏观:将 右子树接回根结点
        }else{// ch  为 ‘#’ 字符时,代表空树【也就是null 节点】
            i++;// 不用管,直接加加。
        }
        // 如果 ch == ‘#’,刚好 我们 root 就是 null
        // 如果 ch  是 字符数据,不用担心,if语句帮我们处理好了。
        return root;
    }
    // 中序遍历【这个简单,我就先写了】
    public static void inorderTraversal(TreeNode root){
        if(root == null){
            return;
        }
        inorderTraversal(root.left);
        System.out.print(root.val + " ");
        inorderTraversal(root.right);
    }
}

;

LeetCode - 102. 二叉树的层序遍历

解题思维

这里我们需要 了解/实现 一下 层序遍历的代码。
思维: 跟前面判断 完全二叉树的方法一致:用队列实现。出队的时候,随便打印一下,不同的是 如果左右子树为空树,就不用入队了。【没有值可以打印】

代码如下【不是题目的最终答案 - 过渡】

代码 如下 - 这是真货

因为题目很gou!要求的 返回值,很那啥。
把 每一层的节点数据,先存入一个 线性表 中。
然后,根据层次顺序,存入 另一个线性表中。【你可以理解为 是 一个 二维数组】

但是,有一个问题:我们前面写的 层序遍历,是没有分层的。
只要出队的元素不为null,我就打印一下。
来下面的代码,看看我是怎么处理的

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if(root == null){
            return result;
        }
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        while(!q.isEmpty()){
            int size = q.size();// 记录当前队列的元素个数
            List<Integer> list = new ArrayList<>();// 存储当前层次的元素
            while(size > 0){// 将当前层次的元素 出队
                TreeNode tmp = q.poll();// 出队
                list.add(tmp.val);//  存入 list 中
                size--;// 表示 将当前层次的元素存入完成。
                // 同时,将该层次节点 的 下一层次的节点(左右子树) 存入
                if(tmp.left != null){
                    q.offer(tmp.left);
                } 
                if(tmp.right != null){
                    q.offer(tmp.right);
                }
            }
            result.add(list);// 将每层的 list 存入 result 中
        }
        // 返回 结果
        return result;
    }
}

代码 解析

;

LeetCode - 236. 二叉树的最近公共祖先

;

题目解析

给我们一棵二叉树,和 这棵二叉树中的两个节点,要求我们找到 这两个节点的 公共祖先【父亲节点,注意父亲节点也可以是 二个节点中的一个。也就是说另一个节点是孩子结点】

;

解题思维

;

思维一:假设题目给定的是一棵二叉搜索树

;

代码如下 - 【二叉搜索树得出的某些结论,同样适用于本题】
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        // 终止条件
        if(root == null){
            return root;
        }
        // 根结点为 p 和 q 中一个
        if(root == q || root == p){
            return root == q ? q : p;
        }
        // 在 左子树中 寻找 p 和 q 中的一员 或者 公共祖先【可能没有找到:null】
        // 宏观:在左子树中 p 和 q 的公共祖先
        TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
        // 在 右子树中 寻找 p 和 q中的一员 或者 公共祖先【可能没有找到:null】
          // 宏观:在右子树中 p 和 q 的公共祖先
        TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
        // 如果在 root 的 左右子树寻找结果都不为null,说明 p 和 q 处于不同的子树当中
        if(leftNode != null && rightNode != null){
            return root;// 那么此时的根结点就是  公共祖先
        }
         // 如果在 root 的 左右子树寻找结果,有一个结果为null,说明 p 和 q 处于相同同的子树当中
         // 那么就有可能出现 p 是 q 的 公共祖先,或者 q 是 p 的公共祖先。
         // 在根据递归的性质:每个节点都扮演过根结点的角色
         // 所以,在 p 或 q 的公共祖先的情况下:root 递归到 到这个位置,就会直接返回 root.
         // 再从宏观出发: p 和 q 还有 它们的公共祖先,在一棵子树下
         // 很显然:另一棵子树 是不可能找到公共祖先节点的,所以 root 会递归到 null,并将其作为结果返回。
         // 我们只需去判断一棵子树的结果就够了。
        return  leftNode == null ? rightNode : leftNode;
    }
}

;

代码附图

如果根结点不为公共节点,就去 左右子树中 去找。

如果 根结点 就是 p 和 q 的一员,直接返回根结点。

需要注意的是:有可能 p 和 q 都在 左子树中;或者,都在右子树中。
且,可能存在 p 是 q 的 祖先节点的情况,或者, q 是 p 的 祖先节点情况。;

思维二 - 假设 这个二叉树使用的是孩子双亲表示法来来表示

代码如下
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root == null){
            return root;
        }
        Stack<TreeNode> stackq = new Stack<>();
        Stack<TreeNode> stackp = new Stack<>();
        // 获取路径
        getPath(root,p,stackp);
        getPath(root,q,stackq);
        // 获取栈的大小
        int sizep = stackp.size();
        int sizeq = stackq.size();

        // 利用快慢指针的思想,让栈大的一方先出栈
        // 直到两个栈的大小一致。然后,一起出栈
        // 如果 出栈的元素 相等,此元素就是 最近祖先节点
        int difference = sizep - sizeq;
        if(difference < 0 ){
            difference = sizeq - sizep;
            while(difference>0){
                stackq.pop();
                difference--;
            }
            while(!stackq.isEmpty() && !stackp.isEmpty() ){
                TreeNode nodeq = stackq.pop();
                TreeNode nodep = stackp.pop();
                if(nodeq == nodep){
                    return nodep;
                }
            }
        }else{
            while(difference>0){
                stackp.pop();
                difference--;
            }
            while(!stackq.isEmpty() && !stackp.isEmpty() ){
                TreeNode nodeq = stackq.pop();
                TreeNode nodep = stackp.pop();
                if(nodeq == nodep){
                    return nodep;
                }
            }
        }
        // 表示 这个二叉树 不存在 祖先节点
        return null;
    }
    // 获取 指定节点 的 存储路径
    public boolean getPath(TreeNode root,TreeNode node,Stack<TreeNode> stack){
        if(root == null || node == null){
            return false;
        }
        stack.push(root);
        if(root == node){
            return true;
        }
        boolean flag =getPath(root.left,node,stack);
        if(flag){
            return true;
        }

        flag =getPath(root.right,node,stack);
        if(flag){
            return true;
        }
        stack.pop();
        return false;
    }
}

牛客题霸 - JZ36 二叉搜索树与双向链表

题目解析

将一颗搜索二叉树,进行有序排序【升序】。按照升序的顺序,创建一个链表【双向】。

解题思维

首先通过上面那题思维一,我们得知 搜索二叉树,如果对其进行中序遍历,其结果就是有序的。

最后一个问题: 将其结果转换成一个双向链表 【难点】

代码如下

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null){
            return null;
        }
        // 获取 头节点位置 - 详尽见附图
        inorderTraversal(pRootOfTree);
        TreeNode head = pRootOfTree;
        while(head.left != null){
            head = head.left;
        }
        return head;
    }
    public TreeNode prev ;
    public void inorderTraversal(TreeNode root){
        if(root == null){
            return;
        }
        inorderTraversal(root.left);
        // 打印
        root.left = prev;
        if(prev != null){
            prev.right = root;
        }
        prev = root;
//         System.out.print(root.val + " ");
        inorderTraversal(root.right);
    }
}

附图

LeetCode - 105. 从前序与中序遍历序列构造二叉树

这题的意思,就不用我讲了。跟前面的那道选择题一样【根据 两种遍历方法(前中 或 中后)确定 一棵唯一的二叉树】,不同的是 选择要求返回 另外一种遍历的结果,而这题要我们返回 这棵二叉树的根结点。相同的是 根据两种遍历方式 确定 / 构造 唯一 的 二叉树。

解题思维

根据上面的附图,从而引申出我的解题逻辑。

代码如下
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        // 根据 一种遍历结果是无法确定一棵唯一的二叉树的。
        if(preorder == null ||  inorder == null){
            return null;
        }
        return creationTree(preorder,inorder,0,inorder.length - 1);
    }
    public int rootIndex; // 遍历前序数组的变量,默认值为 0
    public TreeNode creationTree(int[] preorder,int[] inorder,int begin,int end){
        if(begin > end){
            return null;
        }
        TreeNode root = new TreeNode(preorder[rootIndex]);
        int inorderIndex = findIndex(inorder,begin,end,root.val);
        if(inorderIndex == -1){
            return null;
        }
        rootIndex++;
        root.left = creationTree(preorder,inorder,begin,inorderIndex-1);
        root.right = creationTree(preorder,inorder,inorderIndex+1,end);
        return root;  
    }
    private int findIndex(int[] inorder,int begin, int end,int key){
        for(int i = begin;i <= end;i++){
            if(key == inorder[i]){
                return i;
            }
        }
        return -1;
    }
}

LeetCode - 106. 从中序与后序遍历序列构造二叉树

这个就很简单了,就是上面那题改一些地方就行了。
preorder 换成 postorder,rootIndex 要赋值 【postorder.length - 1】
因为 后序遍历: 左 》 右 》根,也就是说:倒数第一个元素 是 根结点,倒数第二个 是 右子树的根结点。【也就是说,先创建 根结点,其次是 右子树,最后左子树】

代码如下

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int rootIndex; // 遍历前序数组的变量,默认值为 0
    public TreeNode buildTree(int[] inorder,int[] postorder) {
        // 根据 一种遍历结果是无法确定一棵唯一的二叉树的。
        if(postorder == null ||  inorder == null){
            return null;
        }

        rootIndex = postorder.length - 1 ;
        
        return creationTree(postorder,inorder,0,inorder.length - 1);
    }
    public TreeNode creationTree(int[] postorder,int[] inorder,int begin,int end){
        if(begin > end){
            return null;
        }
        TreeNode root = new TreeNode(postorder[rootIndex]);
        int inorderIndex = findIndex(inorder,begin,end,root.val);
        if(inorderIndex == -1){
            return null;
        }
        rootIndex--;
        root.right = creationTree(postorder,inorder,inorderIndex+1,end);
        root.left = creationTree(postorder,inorder,begin,inorderIndex-1);
        return root;  
    }
    private int findIndex(int[] inorder,int begin, int end,int key){
        for(int i = begin;i <= end;i++){
            if(key == inorder[i]){
                return i;
            }
        }
        return -1;
    }
}

LeetCode - 606. 根据二叉树创建字符串

题目解析

题目大概的意思:将一个二叉树转换成一个由括号和整数组成的字符串。

代码如下

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public String tree2str(TreeNode root) {
        if(root == null){
            return "()";
        }
        StringBuilder sb = new StringBuilder();
        preOrderChange(root,sb);
        return sb.toString(); 
    }
    public void preOrderChange( TreeNode root, StringBuilder sb ){
        if(root == null){
            return;
        }
        sb.append(root.val);
        if(root.left != null){
            sb.append("(");
            preOrderChange(root.left,sb);
            sb.append(")");
        }else{
            // root.left == null;
            if(root.right == null){
                return ;
            }else{
                sb.append("()");

            }
        }
        if( root.right == null){
            return;
        }else{
            sb.append("(");
            preOrderChange(root.right,sb);
            sb.append(")");
        }
    }

}

拓展题

LeetCode - 144. 二叉树的前序遍历 - 非递归实现

解题思维 —— 借助栈

代码如下

// 非递归
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) { 
        List<Integer> list = new ArrayList();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.isEmpty()){
            while(cur!= null){
                list.add(cur.val);
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode tmp = stack.pop();
            cur = tmp.right;
        }
        return list;
    }
}

LeetCode - 94. 二叉树的中序遍历 - 非递归

有了前序之见。那么,中序将不存在问题。无非就是 list.add 语句换个位置。

代码如下

// 非递归
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
         List<Integer> list = new ArrayList<>();
         Stack<TreeNode> stack = new Stack<>();
         TreeNode cur = root;
         while(cur != null || !stack.isEmpty() ){
             while(cur!=null){
                 stack.push(cur);
                 cur = cur.left;
             }
             TreeNode tmp = stack.pop();
             list.add(tmp.val);
             cur = tmp.right;
         }
         return list;
    }
}

LeetCode - 145. 二叉树的后序遍历- 非递归

代码如下

// 非递归
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode prev = null;// 见附图
        while(cur != null || !stack.isEmpty()){
            while(cur!=null){
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode tmp = stack.peek();
            if(tmp.right == null || prev == tmp.right){// 见附图
                stack.pop();
                list.add(tmp.val);
                prev = tmp;
            }else{
                cur = tmp.right;
            }  
        }
        return list;
    }
}

附图

本文结束

相关文章

微信公众号

最新文章

更多

目录