Java数据结构-二叉树

news2025/1/15 6:42:49

目录

  • 1. 树与二叉树
    • 1.1 树
    • 1.2 二叉树
    • 1.3 树的相关概念
    • 1.4 特殊的二叉树
    • 1.5 二叉树性质
    • 1.6 二叉树的存储与表示方法
  • 2. 二叉树遍历
    • 2.1 前序遍历
    • 2.2 中序遍历
    • 2.3 后序遍历
    • 2.4 层序遍历
  • 3. 二叉树基本操作
    • 3.1 求树的所有结点个数
    • 3.2 求叶子结点个数
    • 3.3 求第k层结点个数
    • 3.4 求二叉树的高度
    • 3.5 检测树中是否存在值为val的结点
    • 3.5 判断树是否为完全二叉树
  • 4. 二叉树相关OJ题
    • 4.1 相同的树
    • 4.2 另一棵树的子树
    • 4.3 翻转二叉树
    • 4.4 二叉树的最大深度
    • 4.5 平衡二叉树
    • 4.6 对称二叉树
    • 4.7 二叉树创建及遍历
    • 4.8 二叉树的层序遍历
    • 4.9 二叉树的最近公共祖先
    • 4.10 已知中序与前序遍历构造二叉树
    • 4.11 已知中序与后序遍历构造二叉树
    • 4.12 根据二叉树创建字符串
    • 4.13 二叉树的前序遍历(递归与非递归)
    • 4.14 二叉树的中序遍历(递归与非递归)
    • 4.15 二叉树的后序遍历(递归与非递归)

老铁们好!今天我们学习一种新的数据结构:二叉树!

1. 树与二叉树

1.1 树

树是一种数据结构,它是由n(n≥0)个有限结点组成一个具有层次关系的集合。它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个结点有零个或多个子节点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树。如图所示:
在这里插入图片描述

1.2 二叉树

二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。二叉树特点是每个结点最多只能有两棵子树,且有左右之分 。二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成,是有序树。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个结点。

1.3 树的相关概念

在这里插入图片描述

结点的度: 一个结点含有的子树个数叫度,如图 结点A的度为2,结点C的度为1,结点G的度为0
树的度: 一棵树中,所有结点度的最大值,如图树的度为2,
叶子结点(终端结点): 度为0的结点叫叶子结点,如图G、H、I、F是叶子结点
孩子结点: 一个结点的子树的根节点叫孩子结点,如图A的孩子结点是C
根节点: 一棵树中,没有双亲结点的结点叫根节点
树的层次: 从根节点开始,根节点为第一层,根节点的孩子为第二层,以此类推
树的高度或深度: 树中所有节点的层次最大值,如图树的高度为4

1.4 特殊的二叉树

满二叉树: 如果一棵二叉树中,所有的结点的度要么是2,要么是0,这棵二叉树就是满二叉树,如图:
在这里插入图片描述
完全二叉树: 一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。如图
在这里插入图片描述

1.5 二叉树性质

1、 一棵非空二叉树的第i层上最多有2 ^ (i-1) (i>0)个结点(规定根节点的层数为1)
2、 深度为k的二叉树的最大结点个数为(2^k )-1(k>=0)
3、 设叶子结点的个数为n0,度为2的结点个数为n2,则有n0 = n2 +1
4、 n个结点的完全二叉树,深度为log2(n+1)向上取整
5、一棵完全二叉树,对每个结点从上到下从左到右进行编号(根节点为0),序号为i的结点:
(1)左孩子结点的编号为2i+1,如果2i+1>n则没有左孩子
(2)右孩子结点的编号为2i+2,如果2i+1>n则没有右孩子
(3)如果i>0,双亲结点的编号为(i-1)/2

1.6 二叉树的存储与表示方法

二叉树的存储形式有两种,一种是链式存储,一种是顺序存储。链式存储是指通过引用来实现的,顺序存储是使用数组来实现的,今天主要介绍链式存储,顺序存储会在堆中介绍到。二叉树表示方法有孩子表示法、双亲表示法

//孩子表示法
class TreeNode {
	int val;//数据域
	TreeNode left;//左孩子的引用
	TreeNode right;//右孩子的引用
}
//双亲表示法
class TreeNode {
	int val;
	TreeNode left;//左孩子的引用
	TreeNode right;//右孩子的引用
	TreeNode parent;//双亲的引用
}

比较常用的是孩子表示法

2. 二叉树遍历

二叉树的遍历分为前序遍历、中序遍历、后序遍历、层序遍历,为了方便理解,我们定义一个类,如下:

public class Tree {
	//树的结点
    static class TreeNode {
        public char val;
        public TreeNode left;//左孩子
        public TreeNode right;//右孩子

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

接着使用枚举的方法创建一棵如图的二叉树,至于如何正确的创建一棵二叉树,我们在后续的OJ题中会介绍到
在这里插入图片描述

    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');
        TreeNode I = new TreeNode('I');
        A.left = C;
        A.right = B;
        C.left = D;
        D.left = G;
        D.right = H;
        B.left = E;
        B.right = F;
        E.left = I;
        return A;
    }

这样我们就创建好了我们想要的二叉树。

2.1 前序遍历

访问顺序:根节点->根的左子树->根的右子树
在这里插入图片描述
前序遍历结果为:A C D G H B E I F

按照前序遍历的规则,访问完A结点后,接着该访问A的左子树,对于红色框部分来说又是一棵树,也需要按照前序遍历的规则访问,同理蓝色部分也是如此,所以我们可以使用递归的方式来遍历
在这里插入图片描述
代码实现:

    //前序遍历
    public void preOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        System.out.print(root.val + " ");//访问根节点
        preOrder(root.left);//递归的方式访问左子树
        preOrder(root.right);//递归的方式访问右子树
    }

2.2 中序遍历

访问顺序:根的左子树->根节点->根的右子树
在这里插入图片描述
中序遍历的结果为:G D H C A I E B F
与前序遍历一样,使用递归的方式遍历
代码实现:

    //中序遍历
    public void inOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        inOrder(root.left);//递归的方式访问左子树
        System.out.print(root.val + " ");//访问根节点
        inOrder(root.right);//递归的方式访问右子树
    }

2.3 后序遍历

访问顺序:根的左子树->根的右子树->根节点
在这里插入图片描述
后序遍历的结果为:G H D C I E F B A
与前序遍历一样,使用递归的方式遍历
代码实现:

    //后序遍历
    public void postOrder(TreeNode root) {
        if (root == null) {
            return;
        }
        postOrder(root.left);//递归的方式访问左子树
        postOrder(root.right);//递归的方式访问右子树
        System.out.print(root.val + " ");//访问根节点
    }

2.4 层序遍历

遍历规则:从根节点开始,第一层的结点,访问完第一层后,接着访问第二层的所有结点(按从左到右的顺序),如图
在这里插入图片描述
层序遍历结果为:A C B D E F G H I
层序遍历的实现,需要借助另一个数据结构:队列
逻辑如图:
在这里插入图片描述

代码实现:

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

3. 二叉树基本操作

3.1 求树的所有结点个数

可以使用递归的思路求解:树的结点个数 = 左子树所有结点个数 + 右子树所有结点个数 + 1(一棵树根节点只有一个),如果树为空则返回0,而递归的结束条件刚好就是root为空
在这里插入图片描述

    //求结点个数
    public int NodeSize(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return NodeSize(root.left) + NodeSize(root.right) + 1;
    }

3.2 求叶子结点个数

递归的思路求解:一棵树的叶子结点个数=左子树的叶子结点个数+右子树的叶子结点个数
递归的结束条件:如果左孩子和右孩子都为空,返回1;如果结点为空,返回0

    //求叶子结点个数
    public int getLeafNodeCount(TreeNode root) {
        if (root == null) {
            return 0;
        }
        if (root.left == null && root.right == null) {
            return 1;
        }
        return getLeafNodeCount(root.left) + getLeafNodeCount(root.right);
    }

3.3 求第k层结点个数

递归的思路求解:第k层的结点个数 = 左子树的第k-1层的结点个数 + 右子树的第k-1层的结点个数
递归的结束条件:如果root为空,返回0;如果k=1,说明已经到达第k层了,返回1

    //第k层结点的个数
    public 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);
    }

3.4 求二叉树的高度

递归的思路求解:二叉树的高度 = max(左子树的高度,右子树的高度)+ 1,因为树的高度是指最大高度,所以是左子树高度和右子树高度取最大的那个+1
递归的结束条件:当root为空时,返回0,空树的高度为0

    //树的高度
    public int getHeight(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int LeftHeight = getHeight(root.left);//左子树的高度
        int RightHeight = getHeight(root.right);//右子树的高度

        return Math.max(LeftHeight, RightHeight) + 1;
    }

3.5 检测树中是否存在值为val的结点

递归的思路求解:可以采用前序遍历,只不过我们把访问换成了比较,思路都是一样的

    public boolean find(TreeNode root, char key) {
        if (root == null) {
            return false;
        }
        //判断根结点
        if (root.val == key) {
            return true;
        }
        //判断左子树
        boolean flg1 = find(root.left,key);
        if (flg1) {
            return true;
        }
        //判断右子树
        boolean flg2 = find(root.right,key);
        if (flg2) {
            return true;
        }
        return false;
    }

3.5 判断树是否为完全二叉树

代码:

    //是否为完全二叉树
    public boolean isCompleteTree(TreeNode root) {
        if (root == null) {
            return false;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode top = queue.poll();
            if (top == null) {
                break;
            }
            queue.offer(top.left);
            queue.offer(top.right);
        }    
        while (!queue.isEmpty()) {
            TreeNode tmp = queue.peek();
            if (tmp != null) {
                return false;
            } else {
                queue.poll();
            }
        }

        return true;
    }

思路与层序遍历的逻辑有点类似,只不过这次不管top的左和右是不是空,我们都入队,如果top为空循环结束。如果树是完全二叉树,此时队列中剩余的元素全是null,如果队列中有不为null的元素,说明这棵树不是完全二叉树(如图)
在这里插入图片描述
在这里插入图片描述

4. 二叉树相关OJ题

4.1 相同的树

在这里插入图片描述
题目链接: 相同的树
题目要求: 给定2个二叉树的根节点p、q,判断这两棵树是不是同一棵树(树的结构一样、每个结点的值一样,说明是同一棵树)
解题思路: 递归思路:先判断两棵树的根节点结构是否相同,有两种情况:两个结点都为空、一个结点为空一个结点不为空、两个结点都不为空。如果两个结点都为空,认为两棵树是相同的;如果一个结点为空一个结点不为空,认为两棵树不是相同的;如果两个结点都不为空,说明结构是相同的。结构相同之后,判断两个结点的值是否相同,如果相同,证明了两棵树根节点是相同的,接着需要递归判断两棵树的左子树、右子树是否相同
代码:

    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) {
            // p、q都为空,认为是相同的树
            return true;
        }
        if (p == null || q == null) {
        	//一个为空,一个不为空,此时结构不相同,不是相同的树
            return false;
        }
        //以上代码走完,证明两个根节点的结构是相同的,还需要判断两个结点的值是否相同
        if (p.val != q.val) {
            // 根节点的val不同,返回false
            return false;
        } else {
            // 此时根节点判断结束,接着递归判断左右子树
            return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
        }
    }

4.2 另一棵树的子树

在这里插入图片描述
题目链接:另一棵树的子树
题目要求: 给定两棵树的根节点:root、subRoot,判断subRoot这棵树是否为root这棵树的子树
解题思路: 递归思路:判断subRoot和root是不是相同的树、判断subRoot是不是root的左子树的子树、判断subRoot是不是root的右子树的子树,三个条件只要满足一个就认为subRoot是root的子树

    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if (root == null) {
            return false;
        }
        return isSubtree(root.left, subRoot) ||
                isSubtree(root.right, subRoot) || isSametree(root, subRoot);
    }

    public boolean isSametree(TreeNode p, TreeNode q) {
        if (p == null && q == null) {
            return true;
        }
        if (p == null || q == null) {
            return false;
        }
        if (p.val != q.val) {
            return false;
        }
        return isSametree(p.left, q.left) && isSametree(p.right, q.right);
    }

4.3 翻转二叉树

在这里插入图片描述
题目链接: 翻转二叉树
题目要求: 给定一棵二叉树的根节点,要求翻转这棵树
解题思路: 交换根节点的左右结点,翻转完根节点的左右结点,接着递归翻转左子树与右子树
代码:

    public TreeNode invertTree(TreeNode root) {
        if (root == null) {
            return root;
        }
        if (root.left == null && root.right == null) {
            return root;
        }
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
        invertTree(root.left);
        invertTree(root.right);
        return root;
    }

4.4 二叉树的最大深度

在这里插入图片描述
题目链接: 二叉树的最大深度
题目要求: 求二叉树的最大深度
解题思路: 递归思路:二叉树的最大深度 = 左子树最大深度与右子树最大深度的最大值 + 1,递归的结束条件为:当root=null时,返回0
代码:

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

4.5 平衡二叉树

在这里插入图片描述

题目链接: 平衡二叉树
题目要求:
解题思路:
代码:
优化前:

    public boolean isBalanced(TreeNode root) {
        if (root == null) {
            return true;
        }
        
        if(Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1) {
            //if 语句:判断根节点这棵树是否为平衡二叉树,如果根节点这棵树是,则需要递归判断左子树和右子树
            return isBalanced(root.left) && isBalanced(root.right);
        }
        return false;
    }
	public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }

以上代码有一个缺点:当第一次递归左子树和右子树的时候就已经知道了左右子树的高度,接着又就行递归求高度,重复计算了树的高度,能不能进行改进?
优化后:

    public boolean isBalanced(TreeNode root) {
        if (root == null) {
            return true;
        }
        return maxDepth(root)>=1;
    }

    public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
        int LeftHeight = maxDepth(root.left);
        if(LeftHeight<0) {
        	//因为代码是递归进行的,LeftHeight可能为-1
            return -1;
        }
        int RightHeight = maxDepth(root.right);
        if(RightHeight<0) {
           //因为代码是递归进行的,RightHeight可能为-1
            return -1;
        }
        if(Math.abs(LeftHeight-RightHeight)<=1) {
            //平衡的,返回树的最大深度即可
            return Math.max(LeftHeight,RightHeight)+1;
        } else {
            //不平衡
            return -1;
        }
    }

4.6 对称二叉树

在这里插入图片描述

题目链接: 对称二叉树
题目要求: 给定一棵二叉树的根节点,判断这棵树是否为对称二叉树,对称的意思是,左右结构相同,结点的值也相同,如实例所示
解题思路: 先判断给定的root是否为空,如果不为空,则进行递归判断,因为题目给的方法只有一个参数,所以我们另写一个isSymmetricChild方法,参数为左结点left,右结点right。在isSymmetricChild方法中进行递归求解。传递的left和right有三种情况:1.两个都为空,此时认为是对称二叉树,2.一个为空一个不为空,此时一定不是对称二叉树,2.两个都不为空,这种情况下,还需要判断left结点和right结点的值是否相等,如果不相等则不是对称二叉树,如果相等,根据示例的二叉树我们可以知道,需要递归判断left的左子树和right的右子树,left的右子树和right的左子树
在这里插入图片描述

代码:

public boolean isSymmetric(TreeNode root) {
        if (root == null) {
            return false;
        }
        return isSymmetricChild(root.left, root.right);
    }

    public boolean isSymmetricChild(TreeNode left, TreeNode right) {
        // 左右:一个为空,一个不为空;两个都为空;两个都不为空
        // 两个都为空,认为也是对称的
        if (left == null && right == null) {
            return true;
        }
        // 一个为空,一个不为空
        if (left == null && right != null || left != null && right == null) {
            return false;
        }
        // 此时,两个都不为空了,判断结点的值是否相同
        if (left.val == right.val) {
            return isSymmetricChild(left.left, right.right) 
            && isSymmetricChild(left.right, right.left);
        } else {
            return false;
        }
    }

4.7 二叉树创建及遍历

在这里插入图片描述

题目链接: 二叉树遍历
题目要求: 给定二叉树的前序遍历的字符串序列,根据这个字符串创建二叉树,并且输出这棵二叉树的中序遍历(#字符表示空树)
解题思路: 首先,题目只告诉了我们前序遍历,能不能确定它是唯一的树?可以!因为它告诉了我们哪个结点是空的!前序遍历我们已经了如指掌,我们只需要知道怎么创建这棵树就好了。因为题目给我们的是前序遍历,那我们也根据前序遍历来创建二叉树。定义一个变量a用于遍历输入的字符串,如果a下标的字符不是#,我们就创建根节点,让根节点的值等于这个字符,接着让a++,接着递归创建左子树和右子树,如果a下标是#,只需要让a++
代码:

class TreeNode {
    char val;//结点的值
    TreeNode left;//左
    TreeNode right;//右

    public TreeNode() {

    }

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


// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static int a;//用于遍历字符串

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

    //前序遍历创建二叉树
    public static TreeNode createTree(String str) {
        char ch = str.charAt(a);
        TreeNode root = null;//创建根节点
        if (ch != '#') {
            root = new TreeNode(ch);
            a++;
            root.left = createTree(str);
            root.right = createTree(str);
        } else {
            a++;
        }
        return root;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String str = new String();
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            str = in.nextLine();
        }
        TreeNode root = createTree(str);
        inOrder(root);
    }
}

4.8 二叉树的层序遍历

在这里插入图片描述

题目链接: 二叉树的层序遍历
题目要求: 给定二叉树的根节点,返回层序遍历的结果,注意返回值的类型,
List<List< Integer>>,实际上就是一个二维数组,
解题思路: 层序遍历我们之前写过了,原理都是一样,只不过我们需要确定每一层的元素个数,定义一个List< Integer >类型的变量 list,然后将某一层的元素都添加到list中,遍历完一层就将list添加至List<List< Integer>>类型的变量ret中
代码:

    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> ret = new LinkedList<>();// 需要返回的
        Queue<TreeNode> queue = new LinkedList<>();// 使用队列来进行层序遍历
        if (root == null) {
            return ret;
        }
        queue.offer(root);

        while (!queue.isEmpty()) {
            int size = queue.size();//每一层的元素个数
            List<Integer> list = new LinkedList<>();
            for (int i = 0; i < size; i++) {
                TreeNode tmp = queue.poll();
                list.add(tmp.val);
                if (tmp.left != null) {
                    queue.offer(tmp.left);
                }
                if (tmp.right != null) {
                    queue.offer(tmp.right);
                }
            }
            ret.add(list);//遍历完一层,将list添加到ret中
        }

        return ret;
    }

4.9 二叉树的最近公共祖先

在这里插入图片描述

题目链接: 二叉树的最近公共祖先
题目要求: 给定一棵二叉树,找到两个指定结点的公共祖先,
解题思路:
可能出现以下情况:
在这里插入图片描述
1.如果p或者q其中一个是等于root,那么公共祖先是root;2.p和q分别树的在左右两边;3.p和q在树的左边;4.p和q在树的右边
方法1: 递归法:第一步判断情况1,如果情况1满足返回root,接着递归左子树和右子树,定义变量LeftTree和RightTree,根据LeftTree和RightTree的值是否为空可以判断其余3种情况
方法2: 类似于链表求公共结点,只要求出根节点到p结点和根节点到q结点的公共结点即可,将根节点到p结点的路径上的所有结点和根节点到q结点的路径上的所有结点分别放入两个栈中,两个栈的元素可能不同,所以先出元素多的栈,出差值个元素,这样两个栈的元素就一样了,接着两个栈一起出元素,判断出栈的元素是否相等,如果相等就说明该结点是公共结点,也就是公共祖先。难点是如何求根节点到p或q的路径上的结点,思路:利用递归求解,在递归的过程中,不管root是不是我们需要的结点,先入栈root结点,判断root是不是指定结点、root的左子树和右子树中有没有指定结点,如果root不是指定的结点、root的左子树和右子树中都没有指定结点,则说明入栈了的这个结点不是我们想要的,此时出栈这个元素,以此类推直到递归结束
在这里插入图片描述

代码:
方法1

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return null;
        }
        if (root == p || root == q) {
            // 情况1
            return root;
        }
        TreeNode LeftTree = lowestCommonAncestor(root.left, p, q);// 递归左边
        TreeNode RightTree = lowestCommonAncestor(root.right, p, q);// 递归右边
        if (LeftTree != null && RightTree != null) {
            return root;//情况2
        }
        if (LeftTree != null) {
            return LeftTree;//情况3
        }
        if (LeftTree == null) {
            return RightTree;//情况4
        }
        return null;
    }

方法2

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
		if (root == null) {
            return root;
        }
        Stack<TreeNode> stackP = new Stack<>();
        Stack<TreeNode> stackQ = new Stack<>();
        getPath(root, p, stackP);
        getPath(root, q, stackQ);
        int sizeP = stackP.size();
        int sizeQ = stackQ.size();
        if (sizeP > sizeQ) {
            int size = sizeP - sizeQ;
            while (size > 0 && !stackP.isEmpty()) {
                stackP.pop();
                size--;
            }
        }
        if (sizeP < sizeQ) {
            int size = sizeQ - sizeP;
            while (size > 0 && !stackQ.isEmpty()) {
                stackQ.pop();
                size--;
            }
        }

        while (!stackP.isEmpty() && !stackQ.isEmpty()) {
            if (stackP.peek().equals(stackQ.peek())) {
                return stackP.peek();
            } else {
                stackP.pop();
                stackQ.pop();
            }
        }

        return null;
    }
    // 获取路径上的所有结点
    public boolean getPath(TreeNode root, TreeNode node, Stack<TreeNode> stack) {
        if (root == null) {
            return false;
        }
        stack.push(root);
        // 判断根节点是否指定结点
        if (root == node) {
            return true;
        }
        boolean left = getPath(root.left, node, stack);
        // 判断左子树是否包含指定结点
        if (left) {
            return true;
        }
        boolean right = getPath(root.right, node, stack);
        // 判断右子树是否包含指定结点
        if (right) {
            return true;
        }
        stack.pop();// 如果根节点,左子树,右子树都没有这个结点,说明它不是路径中的结点,弹出这个元素
        return false;
    }

4.10 已知中序与前序遍历构造二叉树

在这里插入图片描述

题目链接: 已知前序遍历与中序遍历构造二叉树
题目要求: 给定前序遍历和中序遍历的数组,构造二叉树,返回根节点
解题思路: 需要知道的前提是:前序遍历的第一个是结点是根节点,定义preindex,用于遍历preorder数组。ib表示开始位置,ie表示结束位置,如图1:在中序遍历数组中,ib和ie区间内寻找E的下标,接着创建E结点,在中序遍历中,E的左边就是左子树,E的右边就是右子树,E结点创建好之后,递归创建E的左子树和右子树,如图2,递归左子树时,ib不变,ie变成rootIndex-1,递归右子树时,ie不变,ib变成rootIndex+1,所以我们得知道rootindex。递归结束的条件就是ib>ie(当ib=ie表示还有一个结点,不能结束)
在这里插入图片描述
在这里插入图片描述

代码:

class Solution {
    public int preindex;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeChild(preorder, inorder, 0, inorder.length - 1);
    }

    public TreeNode buildTreeChild(int[] preorder, int[] inorder, int ib, int ie) {
        if (ib > ie) {
            return null;
        }
        TreeNode root = new TreeNode(preorder[preindex]);
        int rootIndex = findRootIndex(inorder, ib, ie, preorder[preindex]);
        preindex++;//找到rootIndex之后才能让preindex++
        root.left = buildTreeChild(preorder, inorder, ib, rootIndex - 1);// 递归左
        root.right = buildTreeChild(preorder, inorder, rootIndex + 1, ie);// 递归右
        return root;
    }

	//找rootindex的下标
    private int findRootIndex(int[] arr, int start, int end, int key) {
        for (int i = start; i <= end; i++) {
            if (arr[i] == key) {
                return i;
            }
        }
        return -1;
    }
}

4.11 已知中序与后序遍历构造二叉树

在这里插入图片描述

题目链接: 已知中序与后序遍历构造二叉树
题目要求: 给定后序遍历和中序遍历的数组,构造二叉树,返回根节点
解题思路: 和前序遍历的思路差不多,但是后序遍历的最后一个元素才是根节点,所以需要从后往前遍历,另外,递归的顺序是根、右、左
在这里插入图片描述
在这里插入图片描述

代码:

class Solution {
    public int postIndex;

    public TreeNode buildTree(int[] inorder, int[] postorder) {
        postIndex = postorder.length - 1;
        return buildTreeChild(inorder, postorder, 0, inorder.length - 1);
    }

    public TreeNode buildTreeChild(int[] inorder, int[] postorder, int ib, int ie) {
        if (ib > ie) {
            return null;
        }
        TreeNode root = new TreeNode(postorder[postIndex]);
        int rootIndex = findRootIndex(inorder, ib, ie, postorder[postIndex]);
        postIndex--;
        root.right = buildTreeChild(inorder, postorder, rootIndex + 1, ie);// 递归右
        root.left = buildTreeChild(inorder, postorder, ib, rootIndex - 1);// 递归左
        return root;
    }

    private int findRootIndex(int[] arr, int start, int end, int key) {
        for (int i = start; i <= end; i++) {
            if (arr[i] == key) {
                return i;
            }
        }
        return -1;
    }
}

4.12 根据二叉树创建字符串

在这里插入图片描述

题目链接: 根据二叉树创建字符串
题目要求: 采用前序遍历的方式将二叉树转换为由括号和数字组成的字符串
解题思路:
在这里插入图片描述

代码:

class Solution {
    public String tree2str(TreeNode root) {
        StringBuilder str = new StringBuilder();
        tree2strChild(root,str);
        return str.toString();
    }
    private void tree2strChild(TreeNode root,StringBuilder str) {
        if(root==null) {
            return;
        }
        str.append(root.val);//处理根结点
        //处理左树        
        if(root.left!=null) {
            //root的左不为空
            str.append("(");//拼接左括号
            tree2strChild(root.left,str);//递归左树
            str.append(")");//拼接右括号
        } else {
            //root的左为空
            if(root.right==null) {
                //root右也为空
                return;
            } else{
                //root的右不为空
                str.append("()");
            }
        }
        //处理右树
        if(root.right==null) {
            return;
        } else {
            str.append("(");
            tree2strChild(root.right,str);
            str.append(")");
        }
    }
}

4.13 二叉树的前序遍历(递归与非递归)

在这里插入图片描述
题目链接: 二叉树的前序遍历
题目要求: 给定根节点,返回前序遍历
解题思路: 1.非递归法:利用栈来实现,过程如图,2.递归和之前的递归是一样的这里不多赘述
在这里插入图片描述

代码:
非递归法

    public List<Integer> preorderTraversal(TreeNode root) {        
        List<Integer> list = new LinkedList<>();
        if(root==null) {
            return list;
        }
        //...
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;

        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                list.add(cur.val);
                cur = cur.left;
            }
            TreeNode top = stack.pop();
            cur = top.right;
        }
        return list;
    }

递归法

    List<Integer> list = new LinkedList<>();
    public List<Integer> preorderTraversal(TreeNode root) {        
        if(root==null) {
            return list;
        }
        list.add(root.val);
        preorderTraversal(root.left);
        preorderTraversal(root.right);
        return list;
    }

4.14 二叉树的中序遍历(递归与非递归)

在这里插入图片描述

题目链接: 二叉树的中序遍历
题目要求: 给定根节点,返回中序遍历
解题思路: 1.非递归法:利用栈来实现,过程如图,2.递归和之前的递归是一样的这里不多赘述
在这里插入图片描述

代码:
非递归法

    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<>();
        //.........
        if(root==null) {
            return list;
        }
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            //左走完了
            TreeNode top = stack.pop();
            //
            list.add(top.val);
            //再往右
            cur = top.right;
        }
        return list;
    }

递归法

    List<Integer> list = new LinkedList<>();
    public List<Integer> inorderTraversal(TreeNode root) {
    if (root == null) {
    return list;
    }
    inorderTraversal(root.left);
    list.add(root.val);
    inorderTraversal(root.right);
    return list;
    }

4.15 二叉树的后序遍历(递归与非递归)

在这里插入图片描述

题目链接: 二叉树的后序遍历
题目要求: 给定根节点,返回后序遍历
解题思路: 1.非递归法:利用栈来实现,过程如图,2.递归和之前的递归是一样的这里不多赘述
在这里插入图片描述

代码:
非递归法

public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> list = new LinkedList<>();
        if(root==null) {
            return list;
        }
        //.................
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode flg = null;
        while (cur != null || !stack.isEmpty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.peek();
            if (top.right == null || top.right == flg) {
                stack.pop();
                list.add(top.val);
                flg = top;
            } else {
                cur = top.right;
            }
        }
        return list;
    }

递归法

    List<Integer> list = new LinkedList<>();
    public List<Integer> postorderTraversal(TreeNode root) {
        if(root==null) {
            return list;
        }
        postorderTraversal(root.left);
        postorderTraversal(root.right);
        list.add(root.val);
        return list;
    }

今天的内容就到这里,感谢大家的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1609103.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

笔记-----BFS宽度优先搜索

对于BFS&#xff1a;宽搜第一次搜到就是最小值&#xff0c;并且基于迭代&#xff0c;不会爆栈。 Flood Fill 模型 如果直译的话就是&#xff1a;洪水覆盖&#xff0c;意思就是像是从一个点一圈圈的往外扩散&#xff0c;如果遇见能够连通的就扩散&#xff0c;如果遇见无法联通的…

医院的工作手机、平板等智能终端的安全管理

移动互联网的时代&#xff0c;在医疗行业&#xff0c;特别是各级省市的医院&#xff0c;在各个科室的日常办公及业务开展都依赖计算机及智能终端设备如平板、手机等&#xff0c;这样能提高医务人员的办公效率&#xff0c;从而更好的为患者服务。 在医院信息化发展过程中&#…

vue中使用水印

1. 在utils下创建watermark.js const watermark {}/**** param {要设置的水印的内容} str* param {需要设置水印的容器} container* param {需要设置水印的每一块的宽度} canWidth* param {需要设置水印的每一块的高度} canHeight* param {需要设置水印的字体} canFont* para…

line 1:20 no viable alternative at input ‘point,‘

背景 遇到一个蛋疼的问题&#xff0c;搞得老夫难受的一&#xff0c;解决了索性记录下 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession61b0005f] was not registered for synchronization because synchronization is not activ…

C# 报输入字符串格式不正确的原因

先放错误代码 23 class Voicewater 24 { 25 public void voicealarm(int tem) 26 { 27 Console.WriteLine("现在的温度是{}度了",tem); 28 } 29 } 解决方法…

盲人盲杖:科技革新,助力视障人士独立出行

在我们的社会中&#xff0c;盲人朋友们以其坚韧的精神风貌&#xff0c;生动诠释着生活的多样与可能。然而&#xff0c;当我们聚焦于他们的日常出行&#xff0c;那些普通人视为寻常的街道、路口&#xff0c;却成为他们必须面对的严峻挑战。如何切实提升盲人盲杖的功能&#xff0…

PostgreSql-Install

PostgreSql源码安装 一、源代码下载二、操作系统配置三、编译安装四、启动数据库五、相关命令 PostgreSQL是一个强大的 开源对象关系数据库系统&#xff0c;它使用并扩展了SQL语言&#xff0c;并结合了许多功能&#xff0c;可以安全地存储和扩展最复杂的数据工作负载。 一、源…

【C语言】指针详解(五)

目录 1.字符指针 1.1常量字符串 2.指针数组 3.数组指针 1.字符指针 字符指针就是指向字符的指针&#xff0c;字符指针可以存储字符变量的地址。 举例如下&#xff1a; 定义指针变量pa存a的地址&#xff0c;改变*pa的值&#xff0c;a也会随之改变 。 1.1常量字符串 &#x1f…

开发工具——postman使用教程详解

一、概念 1、Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件&#xff0c;Postman分为Postman native app和Postman Chrome app两个版本。目前Chrome app已停止维护&#xff0c;官方不推荐使用该版本。 2、官网下载地址&#xff1a;http://www.getpostman.com…

MATLAB实现禁忌搜索算法优化柔性车间调度fjsp

禁忌搜索算法的流程可以归纳为以下几个步骤&#xff1a; 初始化&#xff1a; 利用贪婪算法或其他局部搜索算法生成一个初始解。清空禁忌表。设置禁忌长度&#xff08;即禁忌表中禁止操作的期限&#xff09;。邻域搜索产生候选解&#xff1a; 通过特定的搜索算子&#xff08;如…

微信小程序地图polyline坐标太多异常显示BUG

描述 微信小程序map地图上显示polyline线&#xff0c;点位超过1250个出现bug&#xff0c;&#xff08;仅真机上出现&#xff0c;模拟器上正常&#xff09; 这里以加载四川省边界为例, 以下是示例代码 // 读取geojson数据 uni.request({url: https://geo.datav.aliyun.com/a…

用 element ui 实现季度选择器

由于在数据项目中经常以各种时间条件查询数据&#xff0c;所以时间选择器&#xff08;DatePicker&#xff09;组件是很常用的组件。但是在我使用的 Element UI 中&#xff0c;缺少了季度选择器的功能。 简易实现 一开始我根据时间范围使用 select 去遍历,如 2024-Q1、2023-Q4…

Node.js 基础学习

文章目录 1. Node.js1.1 是什么&#xff1f;1.2 作用 2. 命令行工具2.1 命令的结构2.2 常用命令 3. Node.js 注意点3.1 Node.js 中不能使用DOM 和BOM 的API3.2 Node.js 中顶级对象叫做global 4. Buffer4.1 Buffer 特点4.2 Buffer 创建方式4.3 Buffer 操作与注意点 5. 计算机基础…

Rust入门-引用借用

一、引用借用&#xff0c;是什么、为什么、怎么用 所有权上篇我们已经讨论过了&#xff0c;所以这篇我们讨论Rust的引用借用 1、引用借用 是什么&#xff1f; Rust 通过借用(Borrowing) 这个概念来达成上述的目的&#xff0c;获取变量的引用&#xff0c;称之为借用(borrowin…

CERLAB无人机自主框架: 1-环境搭建

前言&#xff1a;更多更新文章详见我的个人博客主页【MGodmonkeyの世界】 描述&#xff1a;欢迎来到CERLAB无人机自主框架&#xff0c;这是一个用于自主无人飞行器 (UAV) 的多功能模块化框架。该框架包括不同的组件 (模拟器&#xff0c;感知&#xff0c;映射&#xff0c;规划和…

【数据结构-串-数组-广义表】

目录 1 串-理解1.1 串的抽象定义&#xff1a;-理解1.2 串的存储结构-不断掌握1.2.1 顺序存储结构&#xff1a;1.2.2 链式存储结构&#xff1a; 1.3 串的模式匹配算法&#xff1a;-掌握1.3.1 BF暴力求解算法-代码 -掌握1.3.2 KMP求解算法-代码--掌握 2 数组-不断掌握2.1 顺序存储…

vue-cli2 与vue-cli3,vue2与vue3 初始化项目,本地vue项目,详细解析区别(2024-04-19)

目录 1、区别&#xff08;vue-cli2 与 vue-cli3 &#xff09; 2、例子1&#xff08;vue2项目&#xff09; 2.1 版本与命令行 2.2 项目本地截图 2.3 项目文件解析 &#xff08;1&#xff09;package.json 文件 &#xff08;2&#xff09;webpack.dev.conf.js文件 &#…

PTA图论的搜索题

目录 7-1 列出连通集 题目 输入格式: 输出格式: 输入样例: 输出样例: AC代码 7-2 六度空间 题目 输入格式: 输出格式: 输入样例: 输出样例: 思路 AC代码 7-3 地下迷宫探索 题目 输入格式: 输出格式: 输入样例1: 输出样例1: 输入样例2: 输出样例2: 思路 …

股票战法课程之主力的痕迹

文章目录 1. 主力的操作痕迹2. 主力的建仓2.1 建仓的三种方式2.2 建仓的五个特点2.3 建仓的迹象2.4 建仓的成交量特征 1. 主力的操作痕迹 序号痕迹原因1不跟随大盘节奏筹码都在主力手中2突发利空消息&#xff0c;股价不跌反涨主力被套&#xff0c;不希望散户抛盘3很小的成交量…

【初识Qt】如何使用QtCreator创建项目

文章目录 1 :peach:Qt Creator 概览:peach:2 :peach:使⽤ Qt Creator 新建项目:peach:2.1 :apple:新建项目:apple:2.2 :apple:使用标签实现 Hello World 程序:apple:2.2.1 :lemon:纯代码方式实现:lemon:2.2.2 :lemon:可视化操作实现:lemon: 2.3 :apple:使用按钮实现 Hello Worl…