题目1:实现二叉树的先序、中序、后序遍历[递归方式和非递归方式]
1.1递归实现
public class BinaryTreeWithRecur {
public static class Node{
public int value;
public Node left;
public Node right;
public Node(int value){
this.value=value;
}
}
//先序遍历
public void preOrderRecur(Node head){
if(head==null){
return;
}
System.out.print(head.value+" ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
//中序遍历
public void inOrderRecur(Node head){
if(head==null){
return;
}
inOrderRecur(head.left);
System.out.print(head.value+" ");
inOrderRecur(head.right);
}
//后序遍历
public void postOrderRecur(Node head){
if(head==null){
return;
}
postOrderRecur(head.left);
postOrderRecur(head.right);
System.out.print(head.value+" ");
}
//测试
public static void main(String[] args) {
BinaryTreeWithRecur tree=new BinaryTreeWithRecur();
Node root=new Node(1);
Node node1=new Node(2);
Node node2=new Node(3);
Node node3=new Node(4);
Node node4=new Node(5);
Node node5=new Node(6);
Node node6=new Node(7);
root.left=node1;
root.right=node2;
node1.left=node3;
node1.right=node4;
node2.left=node5;
node2.left=node6;
System.out.println("前序遍历:");
tree.preOrderRecur(root);
System.out.println();
System.out.println("中序遍历:");
tree.inOrderRecur(root);
System.out.println();
System.out.println("后序遍历:");
tree.postOrderRecur(root);
}
}
运行结果:
1.2非递归实现
绝大多数可以用递归来解决的问题,其实都可以用另一种数据结构来解决,这种数据结构就是栈。因为递归和栈都有回溯的特性。
用栈结构以非递归的方式来模拟递归方式实现元素前序遍历,如下图所示:
//非递归实现前序遍历
//处理当前结点,有右孩子先压右孩子进栈,有左孩子再压左孩子进栈,
//那么这样弹出就会是先左,再右
public static void preOrderUnRecur(Node head){
if(head==null){
return;
}
Stack<Node> stack=new Stack<>();
stack.push(head);
while(!stack.isEmpty()){
head=stack.pop();
System.out.print(head.value+" ");
if(head.right!=null){
stack.push(head.right);
}
if(head.left!=null){
stack.push(head.left);
}
}
System.out.println();
}
//非递归实现中序遍历
/*
当前节点不为空,当前节点入栈,当前节点向左
当前节点为空,栈顶节点出栈打印,栈顶的节点向右
*/
public static void inOrderUnRecur(Node head){
if(head==null){
return;
}
Stack<Node> stack=new Stack<>();
while(!stack.isEmpty()||head!=null){
if(head!=null){
stack.push(head);
head=head.left;
}else{
head=stack.pop();
System.out.print(head.value+" ");
head=head.right;
}
}
System.out.println();
}
//非递归实现后序遍历
//先序中左右,先处理当前结点,然后改为先压左孩子,再压右孩子,
//那么弹出的顺序就成为了右左【中右左】,利用一个栈即可变为左右中
public static void postOrderUnRecur(Node head){
if(head==null){
return;
}
Stack<Node> stack1=new Stack<>();
Stack<Node> stack2=new Stack<>();
stack1.push(head);
while(!stack1.isEmpty()){
head=stack1.pop();
stack2.push(head);
if(head.left!=null){
stack1.push(head.left);
}
if(head.right!=null){
stack1.push(head.right);
}
}
while(!stack2.isEmpty()){
System.out.print(stack2.pop().value+" ");
}
System.out.println();
}
题目2:在二叉树中找到一个节点的后继节点
【题目】 现在有一种新的二叉树节点类型如下:
public class Node {
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int data) {
this.value = data;
}
}
- 二叉树节点结构多了一个指向父节点的parent指针。假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节点的parent指向null。现在只给一个在二叉树中的某个节点 node,请实现返回node的后继节点的函数。在二叉树的中序遍历的序列中,node的下一个节点叫作node的后继节点,node的上一个节点叫做它的前驱节点。
技巧:
- 1.当前节点有右子树,则找右子树中最左的节点即为当前节点的后继节点;
- 2.当前节点没有右子树,则向上找到一个父节点,看当前节点是否位于该父节点的右子树的,那么这个父节点的父节点即为当前节点的后继节点 。
public class SuccessorNode {
public static class Node{
private int value;
private Node left;
private Node right;
private Node parent; // 新增:父节点指针
public Node(int value){
this.value = value;
}
}
public static Node getSuccessorNode(Node node){
if(node==null){
return node;
}
// 如果该节点存在右子节点,后继节点是右子树中的最左边节点
if(node.right!=null){
return getLeftNodeMost(node.right);
}else{
// 如果当前节点没有右子树,向上查找当前节点属于哪个节点的左子树下面
// 整棵树只有最后一个节点没有后继节点,会查找到根节点的父节点为null
Node parent=node.parent;
while(parent!=null&&parent.left!=node){
node=parent;
parent=node.parent;
}
return parent;
}
}
public static Node getLeftNodeMost(Node node){
if(node==null){
return node;
}
while(node.left!=null){
node=node.left;
}
return node;
}
}
题目3:介绍二叉树的序列化和反序列化
序列化:记录二叉树结构。反序列化:还原二叉树结构。
- “_”:用于分开节点中的值;
- “#”:用于表示null空节点,用这些符号表示null节点把位置给占住,不然无法区分一些节点值都相等的情况。
以前序遍历为例,利用递归将二叉树序列化和反序列化:
public class SerializeAndReconstuct {
public static class Node{
private int val;
private Node left;
private Node right;
public Node(int val){
this.val = val;
}
}
// 前序遍历 序列化二叉树
public String serialByPre(Node head){
if(head == null){
return "#_"; // 空节点用#表示,不同节点值之间用_隔开
}
String res = head.val + "_"; // 中
res += serialByPre(head.left); // 左
res += serialByPre(head.right); // 右
return res;
}
// 前序遍历 反序列化二叉树
public Node reconByPreString(String str){
String[] values = str.split("_"); // 将字符串分割成节点值组成的数组
Queue<String> queue = new LinkedList<String>();
for(String i : values){
// 将数组中的节点元素添加到队列中,也可以直接使用数组
queue.add(i);
}
return reconPreOrder(queue);
}
// 传入参数:队列 返回值:Node
public Node reconPreOrder(Queue<String> queue){
String val = queue.poll();
if(val.equals("#")){
return null; // 如果值为#,则构建一个空节点
}
// 构建二叉树
Node head = new Node(Integer.valueOf(val));
head.left = reconPreOrder(queue);
head.right = reconPreOrder(queue);
return head;
}
}
以前序遍历为例,利用层次将二叉树序列化和反序列化:
import java.util.LinkedList;
import java.util.Queue;
public class SerializeAndReconstructByLevel {
public static class Node{
private int val;
private Node left;
private Node right;
public Node(int val){
this.val = val;
}
}
// 按层序列化
public String serializeByLevel(Node head){
if(head == null){
return "#_";
}
String res = head.val + "_";
Queue<Node> queue = new LinkedList<Node>();
queue.offer(head);
while(!queue.isEmpty()){
// 中
head = queue.poll();
// 左
if(head.left != null){
res += head.left.val + "_";
queue.offer(head.left);
}else{
res += "#_";
}
// 右
if(head.right != null){
res += head.right.val + "_";
queue.offer(head.right);
}else{
res += "#_";
}
}
return res;
}
// 按层反序列化
public Node recornByLevelString(String levelStr){
String[] values = levelStr.split("_");
int index = 0;
Node head = generateNodeByString(values[index++]);
Queue<Node> queue = new LinkedList<Node>();
if(head != null){
queue.offer(head); // 中
}
Node node = null;
while(!queue.isEmpty()){
node = queue.poll();
node.left = generateNodeByString(values[index++]);
node.right = generateNodeByString(values[index++]);
if(node.left != null){
queue.offer(node.left); // 左
}
if(node.right != null){
queue.offer(node.right); // 右
}
}
return head;
}
// 根据字符串构建一个节点
public Node generateNodeByString(String str){
if(str.equals("#")){
return null;
}
return new Node(Integer.valueOf(str));
}
}
题目4:判断一棵二叉树是否是平衡二叉树
平衡二叉树:对任何一棵树,其左右子树高度差不超过1
思路:只要以每个结点作为根节点的树都是平衡的,则整棵树就是平衡的
- 1.左子树不平衡?
- 2.右子树不平衡?
- 3.都平衡,左树和右树的高度差超过1,则不是平衡树
- 4.都平衡,左树和右树的高度差不超过1,则说明是平衡树
递归实现返回信息:左树递归返回左树是否平衡和左树的高度,右树同样。
public class IsBalanceTree {
public static class Node{
private int val;
private Node left;
private Node right;
public Node(int val){
this.val = val;
}
}
// 构建递归过程中的返回值结构
public class ReturnData{
public boolean isB; // 是否平衡
public int h; // 高度
public ReturnData(boolean B, int h){
this.isB = B;
this.h = h;
}
}
// 主函数
public boolean isBalance(Node head){
return process(head).isB;
}
public ReturnData process(Node head){
if(head == null){
return new ReturnData(true, 0);
}
ReturnData leftData = process(head.left); // 得到左子树是否平衡和高度信息
if(leftData.isB == false){
// 当前节点的左子树不平衡,整棵树都不平衡,高度信息没有用了,直接就-1
return new ReturnData(false, -1);
}
ReturnData rightData = process(head.right);
if(rightData.isB == false){
// 当前节点的右子树不平衡,整棵树都不平衡,高度信息没有用了,直接就-1
return new ReturnData(false, -1);
}
// 当前节点的左右子树都平衡,需要对比左右子树的高度差是否大于1
if(Math.abs(rightData.h - leftData.h) > 1){
return new ReturnData(false, -1);
}
// 左右子树都平衡,且高度差小于等于1,则此节点作为根节点的子树是平衡的
// 高度则为左右子树中最高的高度+1
return new ReturnData(true, Math.max(rightData.h, leftData.h) + 1);
}
}
题目5:判断一棵树是否是搜索二叉树、判断一棵树是否是完全二叉树
5.1 搜索二叉树
没有重复结点(有重复的值可以放到同一个节点中,拉个链表),对任何一节点,左子树都比它小,右子树都比它大
判断方法:实际就是中序遍历的结果,如果是依次升序,就是搜索二叉树。只用在中序遍历打印节点的时机进行前一个数和当前数值大小的判断即可。
/*
此代码在非递归中序遍历二叉树代码上改进
*/
public class IsBinarySearchTree {
public static class Node{
private int val;
private Node left;
private Node right;
public Node(int val){
this.val = val;
}
}
// 判断一棵树是否是二叉搜索树
public Boolean isBinarySearchTree(Node head){
if(head == null){
return true; // 空树是二叉搜索树
}
// 这里将值设置为int类型的最小值,因为树里面第一个节点可能存的也是很小的值
int pre = Integer.MIN_VALUE;
Stack<Node> stack = new Stack<Node>();
while(!stack.isEmpty() || head != null){
if(head != null){
while (head != null){
stack.push(head.left); // 压一绺左节点
head = head.left;
}
}else{
// 当前节点为空,说明左边界压完了,则弹出节点(中),再处理右边界
head = stack.pop(); // 中
// 判断前一个数是否小于二叉树
if(pre > head.val){
return false;
}
pre = head.val;
head = head.right; // 右
}
}
return true;
}
}
5.2 完全二叉树:从左往右对齐
判断方法:进行层序遍历每个结点:
- 情况1:左右双全,则看下一个结点
- 情况2:如果一个结点不是左右双全
- 1 如果一个结点无左结点,有右节点,则一定不是完全二叉树
- 2 如果一个结点有左结点,无右节点,则后面遇到的结点必须都是叶节点才能使完全二叉树,否则false
- 3 如果一个结点无左结点,无右结点,则后面遇到的结点必须都是叶节点才能使完全二叉树,否则false
import java.util.LinkedList;
import java.util.Queue;
public class IsCompleteBT {
public static class Node{
private int val;
private Node left;
private Node right;
public Node(int val){
this.val = val;
}
}
public Boolean isCompleteBT(Node head){
if(head == null){
return true;
}
Queue<Node> queue = new LinkedList<Node>();
Boolean afterMustLeaf = false; // 当前节点后面的节点都必须是子节点的开启标志
Node left = null;
Node right = null;
queue.offer(head);
while(!queue.isEmpty()){
head = queue.poll();
// 当开启所有子节点都必须为叶节点时,出现非叶节点,或者出现左子节点为空,右子节点不为空的情况直接返回false
if(afterMustLeaf && (left != null || right != null)
||
(left == null && right != null)){
return false;
}
// 压入左子节点
if(left != null){
queue.offer(left);
}
// 压入右子节点
if(right != null){
queue.offer(right);
}
if(left==null || right==null){
// 前面的节点都是左右双全,但是到这里少了右子节点【左子节点可能有也可能没有】,后序节点都必须为叶节点,开启标志
afterMustLeaf = true;
}
}
return true;
}
}