Java数据结构学习DAY5——二叉树(四)
1、二叉树层序遍历
1.1 层序遍历
-
题目描述
设二叉树的根节点所在层数为1,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第2层上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。 -
解题思路
实现层序遍历,需要借助一个“队列”,
1.先把根节点放到队列里;
2.进行出队列操作,并且访问这个节点;
3.把当前节点的左子树和右子树再入队(空队列就不管了);
4.回到 2 继续循环执行。 -
代码实现
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
}
}
public class lettcode {
public static TreeNode build() {
TreeNode a = new TreeNode(1);
TreeNode b = new TreeNode(2);
TreeNode c = new TreeNode(3);
TreeNode d = new TreeNode(4);
TreeNode e = new TreeNode(5);
TreeNode f = new TreeNode(6);
TreeNode g = new TreeNode(7);
TreeNode h = new TreeNode(8);
TreeNode i = new TreeNode(9);
a.left = b;
a.right = c;
b.left = d;
c.left = e;
c.right = f;
d.left = g;
d.right = h;
e.right = i;
return a;
}
//层序遍历
public static void levelOrder(TreeNode root) {
if (root == null) {
return;
}
//创建一个队列
Queue<TreeNode> queue = new LinkedList<>();
//把根节点入队列
queue.offer(root);
//循环取队列队首元素
while (true) {
TreeNode cur = queue.poll();
if (cur == null) {
break;
}
//访问当前节点,直接打印
System.out.print(cur.val);
//把左右子树入队列
if (cur.left != null) {
queue.offer(cur.left);
}
if (cur.right != null) {
queue.offer(cur.right);
}
}
}
public static void main(String[] args) {
TreeNode root = build();
levelOrder(root);
}
}
- 结果
1.2 判断一颗树是不是完全二叉树
- 解题思路
1.针对这个完全二叉树进行层序遍历
2.判定这个二叉树是不是完全二叉树,需要分两个阶段来判定,
a)第一阶段、要求每个被访问的节点都要有两个子树。
若当前节点只有左子树,进入第二阶段;
若当前节点没有子树,也进入第二阶段;
若当前节点没有右子树,则直接认为此树不是完全二叉树
b)后面的节点都没有子树 - 代码实现
public static boolean isCompleteTree( TreeNode root) {
if (root == null){
return true;
}
//层序遍历
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
//为 ture 表示是第一阶段。如果是 false 表示第二阶段
boolean isLevel1 = true;
while (true) {
TreeNode cur = queue.poll();
if (cur == null) {
break;
}
if (isLevel1){
//第一阶段的判定。要求同时具有左右子树
if (cur.left != null && cur.right != null) {
//继续遍历
queue.offer(cur.left);
queue.offer(cur.right);
} else if (cur.left == null && cur.right != null) {
return false;
} else if (cur.left != null && cur.right == null) {
//还需要继续判定,进入第二阶段了
isLevel1 = false;
queue.offer(cur.left);
} else {
//cur.left 和 cur.right 都为 null
//进入第二阶段
isLevel1 = false;
}
} else {
//第二阶段的判定,只要发现该节点有子树,则认为这棵树不是完全二叉树。
if (cur.left != null || cur.right != null) {
return false;
}
}//end if
}//end while
//当遍历完了之后,都没有找到反例,就认为是完全二叉树。
return true;
}
public static void main(String[] args) {
TreeNode root = build();
boolean result = isCompleteTree(root);
System.out.println(result);
}
}
- 结果
2、 进阶面试题
- 二叉树的构建及遍历。
- 二叉树的分层遍历 。
- 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。
- 二叉树搜索树转换成排序双向链表。
- 根据一棵树的前序遍历与中序遍历构造二叉树。
- 二叉树创建字符串。
2.1二叉树的构建及遍历。
- 题目描述
编一个程序,读入用户输入的一串先序遍历字符串,根据此字符串建立一个二叉树(以指针方式存储),例如如下的先序遍历字符串:ABC##DE#G##F### ,其中“#”表示的是空格,空格字符代表空树。建立起此二叉树以后,再对二叉树进行中序遍历,输出遍历结果。
输入描述:输入包括1行字符串,长度不超过100。
输出描述:可能有多组测试数据,对于每组数据,输出将输入字符串建立二叉树后中序遍历的序列,每个字符后面都有一个空格。每个输出结果占一行。
每个字符后面都有一个空格:
每个输出结果占一行。
- 解题思路
核心思路仍然是遍历,也就是进行先序遍历,访问操作不是打印,而是创建节点。 - 代码实现(牛客网)
import java.util.Scanner;
public class Main {
public static class TreeNode {
char val;
TreeNode left;
TreeNode right;
public TreeNode(char val) {
this.val = val;
}
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()) {
String line = scanner.next();
index = 0; //这句代码至关重要!!! //这个代码一旦忘了,就会导致两个用例之间相互影响!
TreeNode root = build(line);
inOrder(root);
System.out.println();
}
}
//通过这个成员变量,记录 build 执行过程中,当前处理到了第几个节点
public static int index = 0;
public static TreeNode build(String input) {
char ch = input.charAt(index);
if (ch =='#') {
//如果是 # 当前节点是一个空节点
return null;
}
//如果当前这个字符不是空节点,就创建出一个 TreedNode 对象
TreeNode root = new TreeNode(ch);
index++;//表示当前节点搞定了,继续处理下一个节点。
root.left = build(input);
index++;
root.right = build(input);
return root;
}
public static void inOrder(TreeNode root) {
if ( root == null) {
return;
}
inOrder(root.left);
System.out.print(root.val + " ");
inOrder(root.right);
}
}
- 结果
2.2 二叉树的分层遍历 。
- 题目描述: 102. 二叉树的层序遍历,给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
- 解题思路
使用先序遍历的方式针对二叉树进行遍历,遍历的同时,在递归方法的参数中加上当前节点的层数信息,
然后在递归方法内部,根据与层数信息,决定 把这个节点加入到对应的哪个数组中 - 力扣代码实现(Java)
/**
* 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 static List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
//为了保证多组用例互相不干扰,需要每次把 result 进行初始化
result = new ArrayList<>();
if (root == null) {
return result;
}
helper(root,0);
return result;
}
public void helper(TreeNode root, int level) {
if (level == result.size()) {
//刚开始创建的 result 长度是 0 , 如果直接 get 取下标
//就会产生下标越界异常
//就需要提前把对应的实例创建好,并且插入到 result 中
result.add(new ArrayList<>());
}
result.get(level).add(root.val);
if (root.left != null) {
helper(root.left, level+1);
}
if (root.right != null) {
helper(root.right, level+1);
}
}
}
- 结果
2.3 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先 。
- 题目描述:236. 二叉树的最近公共祖先,给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
- 解题思路
核心思路仍然是对树进行遍历。给定了 p 和 q,就通过遍历的形式,尝试从当前数组中递归的查找 p 和 q,看能不能找到。
在这个查找过程中如果能同时找到 p 和 q,就说明这个节点就是公共祖先。
最近的公共祖先是哪个?如果从该节点出发进行查找的时候, p 和 q 主要是从 3 个渠道来进行查找:
1) 当前节点是不是 p 或者 q ;
2)看左子树是否包含 p 或者 q;
3)看右子树里是否包含 p 或者 q 。
如果 p 和 q 是来自于这三个渠道中的两个,那么这个节点就是最近的公共祖先。 - 力扣代码实现(Java)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//通过 lca 来记录最终的结果。
public TreeNode lca = null;
//求树中两个节点的最近公共祖先
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
//递归尝试查找 p 和 q
//写一个辅助方法来进行递归
findNode(root, p, q);
return lca;
}
//如果 root 中能找到 p 和 q 找到一个就返回 true
//如果 p 和 q 都没找到,就返回 false
public boolean findNode(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return false;
}
//进行查找,同三个渠道进行查找
//1、判定根节点是不是 p 和 q
int mid = (root == p || root == q) ? 1 : 0;
//2、在左子树中查找p 和 q
int left = findNode(root.left, p, q) ? 1 :0;
//3、在右子树中查找 p 和 q
int right = findNode(root.right, p, q) ? 1 :0;
//最重要的步骤,判定该节点时是否是 lca
//如果 p 和 q 是来自于三和渠道中的两个 ,就认为该节点时 lca
if (mid + right + left == 2) {
lca = root;
}
//方法的最终返回, 找到 p 或者 q 就返回 true
return (mid + left +right) >0;
}
}
- 结果
2.4 二叉树搜索树转换成排序双向链表。
-
题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向
-
解题思路:
1)什么是二叉搜索树?二叉搜索树特点,左子树节点小于根节点;右子树节点大于根节点。
2)核心是中序遍历
a) 先递归的吧左子树转成双向链表;
b)把 根节点尾插到左子树链表自末尾;
c)再递归的把右子树转成双向链表;
d)把根节点头插到右子树的链表前面 -
牛客网代码实现(Java)
/**
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;
}
if (pRootOfTree.left == null && pRootOfTree.right == null) {
return pRootOfTree;
}
//一般情况
//1、先递归的把左子树转成链表
//得到的leftHead 可能是空的。为了防止出现空指针异常。我们加了判断条件。
TreeNode leftHead = Convert(pRootOfTree.left);
//2、把根节点尾插到 leftHead 这个链表中
//需要找到 leftHead 的末尾节点才能尾插
TreeNode leftTail = leftHead;
while (leftTail != null && leftTail.right != null) {
leftTail = leftTail.right;
}
if (leftHead != null) {
leftTail.right = pRootOfTree;
pRootOfTree.left = leftTail;
}
//3、递归的转换右子树
TreeNode rightHead = Convert(pRootOfTree.right);
//4、把当前节点头插到右侧链表的前面
if(rightHead != null) {
pRootOfTree.right = rightHead;
rightHead.left = pRootOfTree;
}
//最终需要返回最终链表的头结点
//注意 leftHead 可能为空,
//如果为空就应该是 pRootOfTree
return leftHead != null ? leftHead : pRootOfTree;
}
}
- 结果
2.5 根据一棵树的前序遍历与中序遍历构造二叉树
-
力扣题目描述: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 int index = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
//由于输入的用例可能有多个
//此处是方法的入口
//把 index 还原成 0 ,防止多个用例之间互相影响。
index = 0;
//辅助方法中多出来的两个参数就表示当前这个子树对应中序遍历结果是啥
//使用这一对下标表示当前子树的中序遍历结果是在 inorder 的哪个部分上
return _buildTree( preorder, inorder,0, inorder.length);
}
//[inorderLeft,inorderRighgt)标注了一个人前闭后开的区间
// inorder 数组上的这个区间中的内容,就是当前子树的中序遍历结果。
public TreeNode _buildTree(int[] preorder, int[] inorder,
int inorderLeft,int inorderRight) {
if(inorderLeft >= inorderRight) {
//以为这当前中序区间是一个空区间
//当前子树是空树
return null;
}
if (index >= preorder.length) {
//先序数组遍历完毕。整个数组处理完毕。
return null;
}
//处理一般情况,仍然是先序遍历
//访问节点操作不是打印,而是构建节点
TreeNode root = new TreeNode(preorder[index]);
//查找一下当前节点,在中序区间处在的位置
//明确了这个位置才好进行下一步递归
int pos = find(inorder, inorderLeft,inorderRight, root.val);
//有了这个 pos 之后,看就知道了当前面左子树的中门后续区间
//[ inorder,pos)
//右子树的中序区间 [pos +1 ,inorderRight)
//递归处理左右子树
//此此处的 index 只 ++ 一次
//如果遍历结果中有 #(空节点), 需要 ++ 两次
//如果没有空节点,只需要 ++ 一次
index++;
root.left = _buildTree(preorder, inorder, inorderLeft, pos);
root.right = _buildTree(preorder, inorder, pos +1, inorderRight);
return root;
}
public int find(int[] arrary, int left, int right, int toFind) {
for (int i = left; i < right; i++) {
if (arrary[i] == toFind) {
return i;
}
}
return -1;
}
}
- 结果
2.6 二叉树创建字符串。
-
题目描述:606. 根据二叉树创建字符串,你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。空节点则用一对空括号 “()” 表示。而且你需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
示例 1:
-
解题思路
核心是先序遍历,打印节点的时候加上()就行、但是需要考虑清楚 () 什么时候可以省略,什么时候不能省略。
如果某个节点,它的左子树和右子树都为空,此时可以省略左右子树的空 ();
如果左子树不为空,右子树为空,也可以省略右子树的空();
左子树为空,右子树不为空,此时不能省略 空()。 -
力扣代码实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public StringBuilder result = new StringBuilder();
public String tree2str(TreeNode t) {
result = new StringBuilder();
if (t == null) {
return "";
}
//通过 helper 方法辅助进行先序遍历递归
// 在递归过程中,逐渐构造出 result 了
helper(t);
result.deleteCharAt(0);
result.deleteCharAt(result.length()-1);
return result.toString();
}
public void helper (TreeNode root) {
if (root == null) {
return;
}
//访问根节点
result.append("(");
result.append(root.val);
helper(root.left);
if(root.left ==null && root.right != null){
result.append("()");
}
helper(root.right);
result.append(")");
}
}
- 结果
3、 前中后序的非递归实现
3.1 二叉树前序非递归遍历实现 。
- 代码实现
//非递归前序遍历二叉树
public static void preOrderByLoop(TreeNode root) {
if (root == null) {
return;
}
//创建一个栈
Stack<TreeNode> stack = new Stack<>();
//把根节点入栈
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur =stack.pop();
System.out.print(cur.val);
if (cur.right != null) {
stack.push(cur.right);
}
if (cur.left !=null){
stack.push(cur.left);
}
}
}
3.2 二叉树中序非递归遍历实现。
- 解题思路:
1)创建一个栈;
2)创建一个引用 cur ,从 root 出发,一路向左跑,遇到的非空节点都入栈,当 cur 遇到 null 的时候,就循环结束;
3)取出栈顶元素,并访问。
(中序的特点就是一路往左走找,遇到某个节点的左子树为空,才能访问根节点);
4)让 cur 指向该节点的右子树,回到 2 继续执行。 - 代码实现
// 非递归中序遍历二叉树
public static void inOrderByLoop(TreeNode root) {
if (root == null) {
return;
}
//创建一个栈
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (true) {
//里层循环负责 cur 往左走并入栈
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
//当上面循环结束 cur 就是 null
//取出栈顶元素并访问
if (stack.isEmpty()) {
//遍历完了
break;
}
TreeNode top = stack.pop();
System.out.print(top.val);
//让 cur 从 top 的右子树出发,重复上述过程。
cur = top.right;
}
}
3.3 二叉树后序非递归遍历实现。
- 代码实现
//非递归后序遍历
public static void postOrderByLoop(TreeNode root) {
if (root == null) {
return;
}
//1. 创建一个栈
Stack<TreeNode> stack = new Stack<>();
//创建爱你一个引用,从 root出发
TreeNode cur = root;
TreeNode prev = cur;
while (true) {
while ( cur != null) {
stack.push(cur);
cur = cur.left;
}
// 4,取出栈顶元素看能不能访问‘
if (stack.isEmpty()){
break;
}
//此树不能直接 pop 改节点能不能访问还不用去夜店
//必须访问了才能出站
TreeNode top = stack.peek();
if (top.right == null || top.right == prev) {
System.out.print(top.val);
prev = stack.pop();
}else {
cur = top.right;
}
}
}
3.4、 非递归前/中/后遍历二叉树完整代码
- 代码:
package java41_0313;
import java.util.Stack;
public class CreateTree {
public static class TreeNode {
char val;
TreeNode left;
TreeNode right;
public TreeNode(char val) {
this.val = val;
}
//创建一颗二叉树
public static TreeNode build() {
TreeNode a = new TreeNode('1');
TreeNode b = new TreeNode('2');
TreeNode c = new TreeNode('3');
TreeNode d = new TreeNode('4');
TreeNode e = new TreeNode('5');
TreeNode f = new TreeNode('6');
TreeNode g = new TreeNode('7');
a.left = b;
a.right = c;
b.left = d;
b.right = e;
e.left = g;
c.right = f;
return a;
}
//非递归前序遍历二叉树
public static void preOrderByLoop(TreeNode root) {
if (root == null) {
return;
}
//创建一个栈
Stack<TreeNode> stack = new Stack<>();
//把根节点入栈
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur =stack.pop();
System.out.print(cur.val);
if (cur.right != null) {
stack.push(cur.right);
}
if (cur.left !=null){
stack.push(cur.left);
}
}
}
// 非递归中序遍历二叉树
public static void inOrderByLoop(TreeNode root) {
if (root == null) {
return;
}
//创建一个栈
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (true) {
//里层循环负责 cur 往左走并入栈
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
//当上面循环结束 cur 就是 null
//取出栈顶元素并访问
if (stack.isEmpty()) {
//遍历完了
break;
}
TreeNode top = stack.pop();
System.out.print(top.val);
//让 cur 从 top 的右子树出发,重复上述过程。
cur = top.right;
}
}
//非递归后序遍历
public static void postOrderByLoop(TreeNode root) {
if (root == null) {
return;
}
//1. 创建一个栈
Stack<TreeNode> stack = new Stack<>();
//创建爱你一个引用,从 root出发
TreeNode cur = root;
TreeNode prev = cur;
while (true) {
while ( cur != null) {
stack.push(cur);
cur = cur.left;
}
// 4,取出栈顶元素看能不能访问‘
if (stack.isEmpty()){
break;
}
//此树不能直接 pop 改节点能不能访问还不用去夜店
//必须访问了才能出站
TreeNode top = stack.peek();
if (top.right == null || top.right == prev) {
System.out.print(top.val);
prev = stack.pop();
}else {
cur = top.right;
}
}
}
public static void main(String[] args) {
TreeNode root = build();
preOrderByLoop(root);
System.out.println();
inOrderByLoop(root);
System.out.println();
postOrderByLoop(root);
}
}
- 结果