🌈目录
- 一、树形结构 🌳
- 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 概念
一棵二叉树是结点的一个有限集合,该集合:
- 或者为空
- 或者是由一个根结点加上两棵别称为左子树和右子树的二叉树组成。
特点:
- 每个结点的子树不超过两个。度<=2
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树(顺序)。
2.2 两种特殊二叉树
满二叉树:每层的结点数都达到最大值(每个结点都有两个子树)每层的结点数最大为2k-1个,总结点数是2k-1。
完全二叉树:从上到下,从左到右,每棵树都紧挨着的。对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从0至n-1的结点一一对应时称之为完全二叉树。满二叉树也是一种特殊的完全二叉树。
2.3 性质
- 若规定根结点的层数为1,则一棵非空二叉树的第i层上最多有 2i-1次方 (i>0)个结点。
- 若规定只有根节点的二叉树的深度为1,则深度为K的二叉树的最大结点数为2k - 1个。(每一层都放满)
- 具有n个结点的完全二叉树的深度k为log2(n + 1)向上取整。
- 对任何一颗二叉树,如果叶子结点个数为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
-
对于具有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 二叉树的遍历
- 前序遍历:访问根结点—>根的左子树—>根的右子树
- 中序遍历:根的左子树—>根节点—>根的右子树
- 后序遍历:根的左子树—>根的右子树—>根节点
遍历十分重要,二叉树的很多题都用到了遍历。
如这张图,他的前序遍历: A B D ɸ ɸ ɸ C E ɸ ɸ F ɸ ɸ ;
中序遍历:ɸ D ɸ B ɸ A ɸ E ɸ C ɸ F ɸ
后续遍历:ɸ ɸ D ɸ B ɸ ɸ E ɸ ɸ F C A
下面这张图就分析了前序遍历,中序和后序遍历基本差不多。
- 层序遍历:设二叉树的根节点所在层数为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.前序遍历:结点 左 右
后序遍历:左 右 结点
从二叉树 前序 和 后序遍历结果规则中可以看出,如果树中每个节点只有一个孩子时,遍历结果肯定是反的。(如果没有左孩子,前:结点 右;后:右 结点;如果没有右孩子,前:结点 左;后:左 结点。
如这个前序: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);
}
另一颗树的子树
- 判断root和subRoot是不是两棵相同的树
- 判断是不是左树的子树
- 判断是不是右树的子树
/**
* 判断是不是另一棵树的子树
* 时间复杂度: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;
}
对称二叉树
- 判断是否都为空
- 判断是否一个为空一个不为空
- 都不为空,判断值是否一样
- 值一样,判断左树的左和右树的右,左树的右和右树的左
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;
}
}
}