详解二叉树

news2024/11/26 12:14:22

🌈目录

  • 一、树形结构​ 🌳
    • 1.1 概念
    • 1.2 其他概念
    • 1.3 树的表示形式
  • 二、二叉树✨
    • 2.1 概念
    • 2.2 两种特殊二叉树
    • 2.3 性质
    • 2.4 二叉树存储
  • 三、二叉树的基本操作🙌
    • 3.1 前置说明
    • 3.2 二叉树的遍历
    • 3.3 二叉树的基本操作
  • 四、二叉树的OJ✍️

一、树形结构​ 🌳

1.1 概念

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

她有一个特殊的结点,叫做根结点,根节点没有前驱结点。

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

树是递归定义的。

注意:1. 子树不相交;2. 除了根结点,每个结点有且只有一个父结点;3. 一颗n个结点的树有n-1条边。

1.2 其他概念

在这里插入图片描述

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

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

叶子结点或终端结点度为0的结点称为叶结点; 如上图:B、C、H、I…等节点为叶结点

双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点

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

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

结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推

树的高度或深度:树中结点的最大层次; 如上图:树的高度为4。深度与层次类似,这棵树最大的深度就是这颗树的高度。

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

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

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

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

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

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

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

1.3 树的表示形式

双亲表示法孩子表示法孩子双亲表示法孩子兄弟表示法等等,下面是孩子兄弟表示法

class Node {
	int value; // 树中存储的数据
	Node firstChild; // 第一个孩子引用
	Node nextBrother; // 下一个兄弟引用
}

在这里插入图片描述

二、二叉树✨

2.1 概念

一棵二叉树是结点的一个有限集合,该集合:

  1. 或者为空
  2. 或者是由一个根结点加上两棵别称为左子树右子树的二叉树组成。

在这里插入图片描述

特点

  1. 每个结点的子树不超过两个。度<=2
  2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树(顺序)。

在这里插入图片描述

2.2 两种特殊二叉树

满二叉树:每层的结点数都达到最大值(每个结点都有两个子树)每层的结点数最大为2k-1个,总结点数是2k-1。

完全二叉树:从上到下,从左到右,每棵树都紧挨着的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。满二叉树也是一种特殊的完全二叉树。

在这里插入图片描述

2.3 性质

  1. 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2i-1次方 (i>0)个结点
  2. 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数为2k - 1个。(每一层都放满)
  3. 具有n个结点的完全二叉树的深度k为log2(n + 1)向上取整。
  4. 对任何一颗二叉树,如果叶子结点个数为n0,度为2的结点个数为n2,则有n0 = n2 + 1。

性质4的推导:

假设二叉树有N个结点,那么N = N0 + N1 + N2①,有N-1条边。

我们知道,度为2的结点会生出2条边,度为1的结点会产生1条边,度为0的结点会产生0条边,

所以N - 1= N2 * 2 + N1②,方程①②联立,解出N0 = N2 + 1

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

    若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点

    若2i+1<n,左孩子序号:2i+1,否则无左孩子

    若2i+2<n,右孩子序号:2i+2,否则无右孩子

已知父亲下标 i,左孩子下标2*i + 1,右孩子下标2*i + 2。

已知孩子坐标 i, 则父亲下标为(i-1)/2。

习题:

1.某二叉树共有 399 个结点,其中有 199 个度为 2 的结点,则该二叉树中的叶子结点数为( )

A 不存在这样的二叉树 B 200 C 198 D 199

N0 = N2 + 1,所以选B

2.在具有 2*n 个结点的完全二叉树中,叶子结点个数为( )

A n B n+1 C n-1 D n/2

n是整数,所以有偶数个结点,又因为是完全二叉树,所以度为1的结点只有1个,所以N = N2 + 1 + 1 + N2 = 2*n

得N2 = n - 1,N0 = N2 + 1 = n,选A

3.一个具有767个节点的完全二叉树,其叶子节点个数为()

A 383 B 384 C 385 D 386

有奇数个结点,所以度为1的结点有0个,所以:

N = N2 + 1 + 0 + N2 = 767

N2 = 383,N0 = N2 + 1 = 384,选B

4.一棵完全二叉树的节点数为531个,那么这棵树的高度为( )

A 11 B 10 C 8 D 12

根据性质3:具有n个结点的完全二叉树的深度k为log2(n + 1)向上取整。

n = 531,k =[log2(532)] ≈ 10,选B。

2.4 二叉树存储

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

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

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

三、二叉树的基本操作🙌

3.1 前置说明

在学习二叉树的基本操作前,需先要创建一棵二叉树,然后才能学习其相关的基本操作。由于现在我们对二叉树结构掌握还不够深入,为了降低学习成本,此处手动快速创建一棵简单的二叉树,快速进入二叉树操作学习,等二叉树结构了解的差不多时,我们反过头再来研究二叉树真正的创建方式。

public class BinaryTree {
    static class TreeNode {
        public char val;
        public TreeNode left;
        public TreeNode right;

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

    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;//返回根节点
    }
}

当然,这并不是创建二叉树的方式,真正创建的方式后面会讲解

3.2 二叉树的遍历

  1. 前序遍历:访问根结点—>根的左子树—>根的右子树
  2. 中序遍历:根的左子树—>根节点—>根的右子树
  3. 后序遍历:根的左子树—>根的右子树—>根节点

遍历十分重要,二叉树的很多题都用到了遍历。

在这里插入图片描述

如这张图,他的前序遍历: A B D ɸ ɸ ɸ C E ɸ ɸ F ɸ ɸ ;

中序遍历:ɸ D ɸ B ɸ A ɸ E ɸ C ɸ F ɸ

后续遍历:ɸ ɸ D ɸ B ɸ ɸ E ɸ ɸ F C A

下面这张图就分析了前序遍历,中序和后序遍历基本差不多。

在这里插入图片描述

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

在这里插入图片描述

他的层序遍历就是:A B C D ɸ E F

那我们来做一下练习,写出下面这棵树的前中后层序遍历

在这里插入图片描述

前:A B D E H C F G

中:D B E H A F C G

后:D H E B F G C A

层:A B C D E F G H

选择:

1.某完全二叉树按层次输出(同一层从左到右)的序列为 ABCDEFGH 。该完全二叉树的前序序列为()

A: ABDHECFG B: ABCDEFGH C: HDBEAFCG D: HDEBFGCA

2.二叉树的先序遍历和中序遍历如下:先序遍历:EFHIGJK;中序遍历:HFIEJKG.则二叉树根结点为()

A: E B: F C: G D: H

3.设一课二叉树的中序遍历序列:badce,后序遍历序列:bdeca,则二叉树前序遍历序列为()

A: adbce B: decab C: debac D: abcde

4.某二叉树的后序遍历序列与中序遍历序列相同,均为 ABCDEF ,则按层次输出(同一层从左到右)的序列为()

A: FEDCBA B: CBAFED C: DEFCBA D: ABCDEF

5.一棵非空的二叉树的先序遍历序列与后序遍历序列正好相反,则该二叉树一定满足( )

A.所有的结点均无左孩子

B.所有的结点均无右孩子

C.只有一个叶子结点

D.至多只有一个结点

1.层序的第一个肯定是根节点,A为根节点,所以在前序遍历的时候第一个肯定为A,排除C D,又因为是完全二叉树,所以根据层序输出,画出这棵树:

在这里插入图片描述

所以他的前序遍历是:A B D H E C F G,选A。

2.先序遍历知道他的根节点,为E,所以选A。那么根据中序遍历,HFI是左子树,JKG是右子树,所以画出这棵树:

在这里插入图片描述

3.根据后序遍历,得知这棵树的根节点为a,他的右子树的根节点是c,根据中序遍历,b为左子树,dce为右子树,那么d是c的左子树,e是c的右子树。所以他的前序遍历是:abcde,选D。

在这里插入图片描述

4.根据后序遍历,F为根结点,所以ABCDE均为左子树,因为中序和后序一样,所以前中后和前后中一样,也就是中后和后中一样,又因为中间结点不可少,所以证明没有后,也就是没有右子树,每个结点都没有右子树。所以这棵树:

在这里插入图片描述

按照层序输出:FEDCBA,选A。

5.前序遍历:结点 左 右

后序遍历:左 右 结点

从二叉树 前序 和 后序遍历结果规则中可以看出,如果树中每个节点只有一个孩子时,遍历结果肯定是反的。(如果没有左孩子,前:结点 右;后:右 结点;如果没有右孩子,前:结点 左;后:左 结点。

img

如这个前序:1 2 3 4 5;后序:5 4 3 2 1

所以,只有一个叶子节点是满足的,选C。

根据前序遍历和后序遍历能不能创建一棵二叉树呢?

不可以!因为前序和后序确定的都是根结点,确定不了左右。

代码实现

//前序遍历
public void preOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        System.out.print(root.val + " ");//先结点
        preOrder(root.left);//再左
        preOrder(root.right);//再右
}


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


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

3.3 二叉树的基本操作

获取树中结点的个数

①遍历过程中,root只要不为空,结点数量+1

②左树结点+右数结点+1

//方法一
public int num = 0;
public int size1(TreeNode root) {
    if (root == null) {
        return 0;
    }
    num++;
    size1(root.left);
    size1(root.right);
    return num;
}

//方法二
public int size(TreeNode root) {
    if(root == null) {
        return 0;
    }
    return size(root.left) + size(root.right) + 1;
}

获取叶子节点个数

//方法一
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);
}

//方法二
public static int leafSize = 0;
void getLeafNodeCount1(TreeNode root) {
    if(root != null && root.left == null && root.right == null) {
        leafSize++;
        getLeafNodeCount1(root.right);
        getLeafNodeCount1(root.left);
    }
}

求第k层结点的个数

根的左树的k-1层 + 右树的k-1层

当k=1时,证明当前层为第k层,返回1

public int getKLevelNodeCount(int k, TreeNode root) {
    if(root == null) {
        return 0;
    }
    if(k == 1) {
        return 1;
    }
    return getKLevelNodeCount(k - 1, root.left) + getKLevelNodeCount(k - 1, root.right);
}

二叉树的高度

高度 = max(左数高度,右树高度) + 1

在这里插入图片描述

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

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

public TreeNode find(TreeNode root, char val) {
    if(root == null) {
        return null;
    }
    if(root.val == val) {
        return root;//root
    }
    TreeNode findLeft = find(root.left, val);//left
    if(findLeft != null) {
        return findLeft;
    }
    return find(root.right, val);//right
}

层序遍历

这个题使用了队列,先进先出,符合层序遍历特点。

遍历结点,如果root不为空,将root放入队列,再将root弹出用top记录,打印root的数值,将top的左和右树放入队列(先放左后放右);再弹出一个结点root用top记录,打印root的数值,将top的左和右树放入队列(如果不为空)(先放左后放右);如此循环,直到所有结点全部被遍历完,队列为空。

public void levelOrder(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<>();
    if(root != null) {
        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);
        }
    }
}

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

类似层序遍历

在这里插入图片描述

public boolean isCompleteTree(TreeNode root) {
    if(root == null) {
        return true;
    }
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    while(!queue.isEmpty()) {
        TreeNode cur = queue.poll();
        if(cur != null) {
            //这里就不用管左树右树是否为空,可以直接放null
            queue.offer(cur.left);
            queue.offer(cur.right);
        } else {
            break;//当队列弹出的元素是null时,停下
        }
    }
    while(!queue.isEmpty()) {
        //遍历剩余元素,如果都是null,证明是完全二叉树
        TreeNode cur = queue.poll();
        if(cur != null) {
            return false;//如果有不是null的,证明不是
        }
    }
    return true;
}

四、二叉树的OJ✍️


相同的树

/**
 * 判断两棵树是否相同
 * 时间复杂度为 O(min(m,n))
 */
public boolean isSameTree(TreeNode p, TreeNode q) {
    if(p == null && q != null || p != null && q == null) {
        return false;
    }
    if(p == null && q == null) {
        return true;
    }
    if(p.val != q.val) {
        return false;
    }
    return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}

另一颗树的子树

  1. 判断root和subRoot是不是两棵相同的树
  2. 判断是不是左树的子树
  3. 判断是不是右树的子树
/**
 * 判断是不是另一棵树的子树
 * 时间复杂度:s*t
 * 每个root结点都要和subRoot判断是否相同
 */
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
    if (root == null) {
        //判断是否为空
        return false;
    }
    if(isSameTree(root,subRoot)) {
        //判断root和subRoot是不是两棵相同的树
        return true;
    }
    return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}//判断是不是左树的子树或者是右树的子树

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

翻转二叉树

每一棵树的左右进行交换

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

平衡二叉树

/**
 * 判断平衡二叉树
 * 时间复杂度为O(n^2)
 * 每个点都要求高度,求高度的复杂度是O(n),n个点,复杂度为O(n^2)
 */
public int getHeight(TreeNode root) {
    if(root == null) {
        return 0;
    }
    return Math.max(getHeight(root.left), getHeight(root.right)) + 1;
}
public boolean isBalanced1(TreeNode root) {
    if(root == null) {
        return true;
    }
    int leftH = getHeight(root.left);
    int rightH = getHeight(root.right);
    return Math.abs(leftH - rightH) < 2 && isBalanced1(root.left) && isBalanced1(root.right);
}

/**
 * 优化
 * 时间复杂度是O(n)
 * 最坏的情况下是求一遍树的高度
 */
public static int getHeight1(TreeNode root) {
    if(root == null) {
        return 0;
    }
    int leftH = getHeight1(root.left);
    int rightH = getHeight1(root.right);
    if(leftH >= 0 && rightH >= 0 && Math.abs(leftH - rightH) <= 1) {
        return Math.max(leftH, rightH) + 1;
    } else {
        return -1;
    }
}
public boolean isBalanced2(TreeNode root) {
    if(root == null) {
        return true;
    }
    return getHeight1(root) >= 0;
}

对称二叉树

  1. 判断是否都为空
  2. 判断是否一个为空一个不为空
  3. 都不为空,判断值是否一样
  4. 值一样,判断左树的左和右树的右,左树的右和右树的左
public boolean isSymmetric(TreeNode root) {
    if(root == null) return true;
    return isSymmetricChild(root.left,root.right);
}
public boolean isSymmetricChild(TreeNode leftTree,TreeNode rightTree) {
    if(leftTree == null && rightTree == null) {
        return true;
    } else if(leftTree == null && rightTree != null || leftTree != null && rightTree == null) {
        return false;
    }
    if(leftTree.val == rightTree.val) {
        return isSymmetricChild(leftTree.left, rightTree.right) && isSymmetricChild(leftTree.right,rightTree.left);
    } else {
        return false;
    }
}

二叉树的构建及遍历

因为题目给的是前序遍历的字符串,我们创建树的时候也必须得根据前序遍历的方式来创建

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 in = new Scanner(System.in);
        String str = in.nextLine();//读取
        TreeNode root = createTree(str);//创建
        inOrder(root);//打印
    }
    /**
     * 二叉树的遍历
     * @param str
     * @return
     */
    public static int i = 0;
    public static TreeNode createTree(String str) {
        TreeNode root = null;
        char c = str.charAt(i);
        if(c != '#') {
            root = new TreeNode(c);//当此结点不为空,可以将它创建成根结点
            i++;
            root.left = createTree(str);//创建左树
            root.right = createTree(str);//创建右树
        } else {
            i++;//结点为空,不用创建
        }
        return root;
    }
    public static void inOrder(TreeNode root) {
        if(root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
    }
}

二叉树的分层遍历1

public List<List<Integer>> levelOrder1(TreeNode root) {
    List<List<Integer>> listAll = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList<>();
    if(root != null) {
        queue.offer(root);
    } else {
        return listAll;
    }
    while(!queue.isEmpty()) {
        int size = queue.size();//记录此时队列大小,即当前层的结点个数
        List<Integer> list = new ArrayList<>();//存放当前层的数据
        while(size != 0) {//遍历当前层的节点
            TreeNode top = queue.poll();
            list.add(top.val);
            if(top.left != null) {
                queue.offer(top.left);
            }
            if(top.right != null) {
                queue.offer(top.right);
            }
            size--;//size等于零的时候,证明当前层的结点遍历完了,但不代表队列是空的,因为在pop当前结点的时候,把他的左树和右树放进队列了(这是下一层的结点)
        }
        listAll.add(list);//添加当前层的list
    }
    return listAll;
}

这棵二叉树的左视图:每层的第一个节点;右视图:每层的最后一个节点

107. 二叉树的层序遍历 II - 力扣(LeetCode)

自底向上的层序遍历

public List<List<Integer>> levelOrder1(TreeNode root) {
    List<List<Integer>> listAll = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList<>();
    if(root != null) {
        queue.offer(root);
    } else {
        return listAll;
    }
    while(!queue.isEmpty()) {
        int size = queue.size();
        List<Integer> list = new ArrayList<>();
        while(size != 0) {
            TreeNode top = queue.poll();
            list.add(top.val);
            if(top.left != null) {
                queue.offer(top.left);
            }
            if(top.right != null) {
                queue.offer(top.right);
            }
            size--;
        }
        listAll.add(0, list);//除了这里不一样,其他均于1相同,在0位置插入元素。
    }
    return listAll;
}

二叉树的最近公共祖先

有三种情况,一是p或者q是root,那么root就是最近公共祖先;二是p和q分别在根节点的左和右子树;三是p和q都在根节点的左子树或者右子树上。

在这里插入图片描述

//法一
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if(root == null) {
        return null;
    }
    if(p == root || q == root) {
        return root;
    }
    TreeNode leftRet = lowestCommonAncestor(root.left, p, q);//在左树找pq
    TreeNode rightRet = lowestCommonAncestor(root.right, p, q);//在右树找pq
    //要么左有右无:pq公共祖先是左子树的根节点
    //要么左无右有:pq公共祖先是右子树的根节点
    //要么左右都有:pq公共祖先是当前的结点
    if(leftRet != null && rightRet != null) {
        return root;
    } else if(leftRet != null) {
        return leftRet;
    } else {
        return rightRet;
    }
}

还有一种方法,借助栈来实现,具体思路是:把从root到p和root到q的所有结点分别放入两个栈,将栈大的那个弹出元素,直到两个栈一样大小,然后再一起弹出元素,直到弹出的元素相等,这个元素就是他们的最近公共祖先。

为什么用栈而不是用队列来实现呢?

因为这个题目要找的是最近的公共祖先,我们知道,栈是后进先出,栈中存放的元素是距离p q越远的越先放入栈中,距离p q越近的越后放入栈中,所以最先弹出的元素是最靠近p q的,找到的也就是最近公共祖先。

而队列是从队头开始出队,弹出的一定是距离p q最远的。

在这里插入图片描述

//法二
public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) {
    if(root == null) {
        return null;
    }
    Stack<TreeNode> stack1 = new Stack<>();
    Stack<TreeNode> stack2 = new Stack<>();
    getPath(root, p, stack1);
    getPath(root, q, stack2);
    int size1 = stack1.size();
    int size2 = stack2.size();

    //弹出多余元素
    if(size1 > size2) {
        int size = size1 - size2;
        while (size != 0) {
            stack1.pop();
            size--;
        }
    } else {
        int size = size2 - size1;
        while (size != 0) {
            stack2.pop();
            size--;
        }
    }
    while (!stack1.empty() && !stack2.empty()) {
        TreeNode val1 = stack1.pop();
        TreeNode val2 = stack2.pop();
        if(val1 == val2) {
            return val1;
        }
    }
    return null;
}
/**
 * 再root这棵树上,找到node这个节点的位置
 */
private boolean getPath(TreeNode root, TreeNode node, Stack<TreeNode> stack) {
    if(root == null) {
        return false;
    }
    stack.push(root);
    if(root == node) {
        return true;
    }
    //如果当前的root不是node,那就找左右树
    boolean ret = getPath(root.left, node, stack);//找左树
    if(ret == true) {//找到了,直接返回
        return true;
    }
    boolean ret2 = getPath(root.right, node, stack);//找右树
    if (ret2 ==true) {
        return true;
    }
    stack.pop();//如果在一棵树的左边和右边都没有找到想要的node,那么证明这棵树的根节点不是我要找node的路径上的节点,那就可以把这个节点弹出,然后返回false表示没找到
    return false;
}

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

前序遍历到的每个结点都是他自己这棵树的根结点,所以先创建根结点,再在中序遍历中找到该结点的位置,该结点的左面就是它的左子树,右边就是右子树。

下面这张图就是整个流程:

在这里插入图片描述

在这里插入图片描述

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildTreeChild(preorder, inorder, 0, inorder.length - 1);
    }
    public int preIndex;//遍历整个前序遍历,必须是全局变量,如果定义成局部变量,那么每次递归她都会被重置成0,达不到遍历的效果
    private TreeNode buildTreeChild(int[] preorder, int[] inorder, int inbegin, int inend) {
        //inbegin:中序遍历的开始
        //inend:中序遍历的结束
        //递归出口
        if(inbegin > inend) {
            //根据图二,我们发现不管是在寻找左子树还是右子树,只要子树为空,inbegin > inend 是一定的,所以我们可以在这设定一个递归结束的条件,为空返回null
            return null;
        }
        TreeNode root = new TreeNode(preorder[preIndex]);//根节点
        int inIndex = findIndex(inorder, inbegin, inend, preorder[preIndex]);//找到中序遍历中,这个根结点的位置,在inbegin和inend间的区间内
        preIndex++;
        //preIndex++之后,先遍历到的结点是此时结点的左子树的根结点,所以先创建左树,再创建右树
        root.left = buildTreeChild(preorder, inorder, inbegin, inIndex - 1);//此结点的左子树等于inbegin到inIndex - 1之间的所有
        root.right = buildTreeChild(preorder, inorder, inIndex + 1, inend);//此结点的右子树等于inIndex + 1到inend之间的所有
        return root;//返回这个结点
    }
    private int findIndex(int[] inorder, int inbegin, int inend, int key){
        //中序遍历,在inbegin到inend中找结点位置
        for(int i = inbegin; i <= inend; i++) {
            if(inorder[i] == key) {
                return i;
            }
        }
        return -1;
    }
}

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

后序遍历 :左 右 根

在后序遍历中从最后往前遍历,先拿到根,再在中序遍历中找根所在的位置

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

    private TreeNode buildTreeChild(int[] postorder, int[] inorder, int inbegin, int inend) {
        //递归出口
        if(inbegin > inend) {
            return null;
        }
        TreeNode root = new TreeNode(postorder[postIndex]);//根节点
        int rootIndex = findIndex(inorder, inbegin, inend, postorder[postIndex]);
        postIndex--;
        //因为postIndex--之后,指向的那个结点是此时结点右子树,所以先创建右树,再创建左树
        root.right = buildTreeChild(postorder, inorder, rootIndex + 1, inend);
        root.left = buildTreeChild(postorder, inorder, inbegin, rootIndex - 1);
        return root;
    }
    private int findIndex(int[] inorder, int inbegin, int inend, int key){
        for(int i = inbegin; i <= inend; i++) {
            if(inorder[i] == key) {
                return i;
            }
        }
        return -1;
    }
}

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

当当前节点不为空,就先拼接它的值,然后开始遍历左树,

如果左树不为空,那就先拼接一个(,然后继续遍历左树的这整棵树,遍历完了就再加上个).

如果左树为空,那就再看一下右树,如果右树不为空,就拼接上(),在表左树为空,如果右树也为空,则可以省略()

遍历完左树就开始遍历右树,如果右树不为空,那就和左树不为空的步骤一样。

如果右树为空,则可以直接省略括号。

在这里插入图片描述

public String tree2str(TreeNode root) {
    if(root == null) {
        return null;
    }
    StringBuilder stringBuilder = new StringBuilder();
    tree2strChild(root, stringBuilder);
    return stringBuilder.toString();

}
private void tree2strChild(TreeNode t, StringBuilder stringBuilder){
    if(t == null) {
        return;
    }
    stringBuilder.append(t.val);
    if(t.left != null) {
        stringBuilder.append("(");
        tree2strChild(t.left, stringBuilder);
        stringBuilder.append(")");
    } else {
        if(t.right == null) {
            return;
        } else {
            stringBuilder.append("()");
        }
    }
    if(t.right != null) {
        stringBuilder.append("(");
        tree2strChild(t.right, stringBuilder);
        stringBuilder.append(")");
    }
}

二叉树前序非递归遍历实现

借助栈,模拟递归过程。

定义一个cur来遍历(前序)结点,只要结点不为空,那么就将他放入栈中,并打印,如果此时cur为空,则弹出栈顶结点,遍历栈顶结点的右边,继续遍历,直到栈为空。

public void preOrder2(TreeNode root) {
    if(root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>();
    TreeNode cur = root;
    while(cur != null || !stack.empty()){
        //这里进入循环的条件不能只有!stack.empty(),否则一开始就进不去循环
        while(cur != null) {
            //不是空就入栈
            stack.push(cur);
            System.out.print(cur.val + " ");
            cur = cur.left;
        }
        //cur == null
        TreeNode top = stack.pop();//弹出栈顶元素
        cur = top.right;//遍历右边
        //又开始循环遍历,直到栈的元素为空
    }
}

二叉树中序非递归遍历实现

和前序遍历的思路是一样的,就是打印的时机变了

左 根 右

public void inOrder2(TreeNode root) {
    if(root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>();
    TreeNode cur = root;
    while(cur != null || !stack.empty()){
        while(cur != null) {
            stack.push(cur);
            //这里不能打印只有左树遍历到尽头才能打印
            cur = cur.left;
        }
        //cur == null
        //证明 左树 没有节点可以遍历了
        TreeNode top = stack.pop();
        //开始弹出打印 根
        System.out.print(top.val + " ");
        //继续遍历右树
        cur = top.right;
    }
}

二叉树后序非递归遍历实现

public void postOrder2(TreeNode root) {
    if(root == null) {
        return;
    }
    Stack<TreeNode> stack = new Stack<>();
    TreeNode cur = root;
    TreeNode prev = null;
    while(cur != null || !stack.empty()){
        while(cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        TreeNode top = stack.peek();
        //再去遍历右树,遍历完右树,再去打印根
        if(top.right == null || top.right == prev) {
            System.out.print(top.val + " ");
            prev = top;//纪录被打印过的点,防止死循环
            stack.pop();
        } else {
            cur = top.right;
        }
    }
}

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

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

相关文章

springboot+vue医院信管系统(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的医院信管系统。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌&a…

Service Control Manager 服务管理器简介

在windows驱动开发流程中&#xff0c;写完sys驱动binary之后&#xff0c;为了让OS能够正确的从注册表中读取到对应的信息&#xff0c;并且将其load运行起来&#xff0c;还需要编写inf文件来描述配置驱动文件。不过这也不是必须的&#xff0c;可以通过ServiceControlManager直接…

Fiddler 抓包工具 - 全网最全最细教程,没有之一

Fiddler 简介 Fiddler 是位于客户端和服务器端的 HTTP 代理 目前最常用的 http 抓包工具之一 功能非常强大&#xff0c;是 Web 调试的利器 监控浏览器所有的 HTTP/HTTPS 流量 查看、分析请求内容细节 伪造客户端请求和服务器响应 测试网站的性能解密 HTTPS 的 Web 会话 全局…

Go语音基于zap的日志封装

zap日志封装 Zap是一个高性能、结构化日志库&#xff0c;专为Go语言设计。它由Uber开源&#xff0c;并且在Go社区中非常受欢迎。它的设计目标是提供一个简单易用、高效稳定、灵活可扩展的日志系统。 以下是Zap的一些主要特点&#xff1a; 1.高性能&#xff1a;Zap的性能非常出…

【Linux】权限的理解

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…

mysql exist和in的区别

一、演示用的表 为了演示二者的区别&#xff0c;先建立两张表 user 表和 order 表 二、in 的执行情况 in在查询的时候&#xff0c;首先查询子查询的表&#xff0c;然后将内表和外表做一个笛卡尔积&#xff0c;然后按照条件进行筛选。所以相对内表比较小的时候&#xff0c;…

接口测试之Jenkins+Jmeter+Ant实现持续集成

安装Jenkins&#xff0c;见手把手教小白安装Jenkins_程序员馨馨的博客-CSDN博客 一&#xff09;Linux机器上安装Jmeter 百度一下就好 二&#xff09;Linux机器上安装Ant 1、下载安装包 进入Apache Ant - Binary Distributions&#xff0c;下载安装包&#xff0c;本次安装的是版…

高仿某东商城flutter版本,个人学习flutter项目

前言 高仿某东商城flutter版本&#xff0c;个人学习flutter项目 使用flutter_redux状态管理网络使用dio进行了简单的封装使用node项目mock服务端接口(mock_server目录)目前只实现了首页&#xff0c;其他功能持续更新… 同款Android Kotlin版本&#xff08; https://github.co…

Mysql索引底层原理及其优化方案

1.深入理解Mysql索引底层数据结构与算法 1.1索引结构 索引及其数据结构&#xff1a; 二叉树红黑树Hash表B-Tree 1.1 二叉树 说明&#xff1a;二叉树是建立数据后&#xff0c;会和第一元素进行比对&#xff0c;当比较的元素小于第一个元素时&#xff0c;此时就会走第一个元素…

代码随想录算法训练营第四十三天 | 填满背包有几种方法、背包有两个维度

1049.最后一块石头的重量II 文档讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;动态规划之背包问题&#xff0c;这个背包最多能装多少&#xff1f;LeetCode&#xff1a;1049.最后一块石头的重量II_哔哩哔哩_bilibili 状态&#xff1a;没想到。 思路 本…

chatgpt如何引入领域知识?mit团队利用gpt4做数据增强来提升小模型在特定领域的效果

一、概述 title&#xff1a;Dr. LLaMA: Improving Small Language Models in Domain-Specific QA via Generative Data Augmentation 论文地址&#xff1a;Paper page - Dr. LLaMA: Improving Small Language Models in Domain-Specific QA via Generative Data Augmentation…

(6)LED点阵屏

LED点阵屏由若干个独立的LED组成&#xff0c;LED以矩阵的形式排列&#xff0c;以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合&#xff0c;如汽车报站器、广告屏以及公告牌等 LED点阵屏分类 按颜色&#xff1a;单色、双色、全彩按像素&#xff1a;88、…

Excel模板导入导出功能测试点

近期接触的都是Web项目&#xff0c;有很多导入数据这个功能&#xff0c;导入的文件格式都是Excel&#xff0c;基本流程就是&#xff1a;下载一个Excel模板&#xff0c;填充数据&#xff0c;再将Excel表格导入&#xff0c;导入后可下载列表&#xff0c;想着这类功能的测试点基本…

springboot基于vue的地方美食分享网站

开发技术介绍 Java介绍 JavaScript是一种网络脚本语言&#xff0c;广泛运用于web应用开发&#xff0c;可以用来添加网页的格式动态效果&#xff0c;该语言不用进行预编译就直接运行&#xff0c;可以直接嵌入HTML语言中&#xff0c;写成js语言&#xff0c;便于结构的分离&…

DVWA全级别通关教程

首先选择难度&#xff0c;我们从low开始&#xff0c;如上图所示进行修改 目录 SQL手工注入 过程&#xff1a; low Medium high Impossible SQL 盲注 过程&#xff1a; SQL 工具注入 工具安装过程&#xff1a; 过程&#xff1a; low Medium High: 暴力破解 过…

教室借用-贪婪算法

问题1&#xff1a; 假设你有一个教室&#xff08;只有一个教室&#xff09;&#xff0c;你有几个来自讲师的使用教室的请求 。应用间隔调度问题来安排尽可能多的请求&#xff0c;以优化教室的使用。 最早开始时间 最早开始时间&#xff08;Earliest Start Time&#xff0c;简…

一站了解zookeeper的关键知识

ZooKeeper 是 Apache 软件基金会的一个软件项目&#xff0c;它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 ZooKeeper 的架构通过冗余服务实现高可用性。 Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高…

maven下载和maven配置(以maven3示例)

maven下载和maven配置&#xff08;以maven3示例&#xff09; 1.maven下载1.1 直接使用idea捆绑的&#xff08;很轻松&#xff09;1.2 手动下载到本机服务器&#xff08;推荐&#xff09;1.2.1 官网下载maven1.2.2 选好版本后&#xff0c;选二进制目录 binaries1.2.3 文件选择1.…

Jmeter的Content-Type设置方式

今天调Jmeter脚本遇到一个问题&#xff1a;接口的请求体为Body Data时&#xff0c;没有在HTTP信息头管理加Content-Type参数&#xff0c;Content-Type: application/json&#xff0c;导致脚本一直跑不通&#xff0c;报错&#xff0c;一顿排查&#xff0c;才发现是请求头的原因。…

SpringCloud项目将某个子模块改成以war的形式打包,需要修改的地方

SpringCloud项目将某个子模块改成以war的形式打包&#xff0c;需要修改的地方 在GAV后面添加 《packaging》 war 《/packaging》 在GAV后面添加 《packaging》 war 《/packaging》 添加war打包方式 <packaging>war</packaging>添加之后项目会报错&#xff0c;工程…