二叉树节点结构:
class Node{
V value;
Node left;
Node right;
}
用递归和非递归两种方式实现二叉树的前序、中序、后序遍历
如何直观的打印一颗二叉树
如何完成二叉树的宽度优先遍历(常见题目:求一颗二叉树的宽度)
用递归和非递归两种方式实现二叉树的前序、中序、后序遍历
递归方式
太简单了,无需赘述
非递归方式
前序遍历:
- 从栈中弹出一个节点cur
- 处理cur
- 先把cur的右孩子节点压入栈,再将cur的左孩子节点压入栈
- 周而复始
中序遍历:
定义一个栈
- 当前节点的左子树(左边界)(不止一个节点)压栈
- 依次弹出节点的过程中打印
- 有右子树的就压栈
- 周而复始
原理:整棵树可以被左边界分解
后序遍历:
准备两个栈,一个是辅助栈,一个是收集栈,先把根节点压辅助栈
- 弹出一个节点cur
- 将cur压进收集栈
- 将cur节点的左右节点压栈,先压左节点
- 周而复始
import java.util.Stack;
public class PreInPosTraversal {
public static class Node{
private int value;
private Node left;
private Node right;
public Node(int value){
this.value = value;
}
}
//递归方法
//前序遍历
public static void preOrderRecur(Node head){
if (head == null){
return;
}
System.out.print(head.value + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
//中序遍历
public static void inOrderRecur(Node head) {
if (head == null) {
return;
}
inOrderRecur(head.left);
System.out.print(head.value + " ");
inOrderRecur(head.right);
}
//后序遍历
public static void posOrderRecur(Node head) {
if (head == null) {
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.print(head.value + " ");
}
//非递归方法
//前序遍历
public static void preOrderUnRecur(Node head){
System.out.println("preOrder:");
if (head != null){
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){
System.out.println("inOrder:");
if (head != null){
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 posOrderUnRecur(Node head){
System.out.println("proOrder:");
if(head != null){
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();
}
public static void main(String[] args) {
Node head = new Node(5);
head.left = new Node(3);
head.right = new Node(8);
head.left.left = new Node(2);
head.left.right = new Node(4);
head.left.left.left = new Node(1);
head.right.left = new Node(7);
head.right.left.left = new Node(6);
head.right.right = new Node(10);
head.right.right.left = new Node(9);
head.right.right.right = new Node(11);
// recursive
System.out.println("==============recursive==============");
System.out.print("pre-order: ");
preOrderRecur(head);
System.out.println();
System.out.print("in-order: ");
inOrderRecur(head);
System.out.println();
System.out.print("pos-order: ");
posOrderRecur(head);
System.out.println();
// unrecursive
System.out.println("============unrecursive=============");
preOrderUnRecur(head);
inOrderUnRecur(head);
posOrderUnRecur(head);
}
}
如何完成二叉树的宽度优先遍历(常见题目:求一颗二叉树的宽度)
宽度优先遍历
public static void w(Node head){
if(head == null){
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while(!queue.isEmpty){
Node cur = queue.poll();
System.out.println(cur.value);
if(cur.left != null){
queue.add(cur.left);
}
if(cur.right!= null){
queue.add(cur.right);
}
}
}
求一颗二叉树的宽度
public static int w(Node head){
if(head == null){
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
HashMap<Node,Integer> levelMap = new HashMap<>();
levelMap.add(head,1);
int curLevel = 1;
int curLevelNodes = 0;
int max = Integer.MIN_VALUE;
while(!queue.isEmpty){
Node cur = queue.poll();
int curNodeLevel = levelMap.get(cur);
if(curNodeLevle == curLevel){
curLevelNodes++;
}else{
max = Math.max(max,curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
if(cur.left != null){
levelMap.put(cur.left,curNodeLevel+1);
queue.add(cur.left);
}
if(cur.right!= null){
levelMap.put(cur.right,curNodeLevel+1);
queue.add(cur.right);
}
}
max = Math.max(max,curLevelNodes);
return max;
}
判断一棵二叉树是否是搜索二叉树
搜索二叉树:左节点的值肯定比右节点的值小(左中右递增)
中序遍历的结果一定是升序
//中序遍历改
public static int preValue = Integer.MIN_VALUE;
public static boolean checkBST(Node head){
if (head == null)
return true;
boolean isLeftBst = checkBST(head.left);
if (!isLeftBst){
return false;
}
if (head.value <= preValue){
return false;
}else {
preValue = head.value;
}
return checkBST(head.right);
}
//非递归中序遍历改
public static int preValue = Integer.MIN_VALUE;
public static boolean unRecursecheckBST(Node head){
if (head != null){
Stack<Node> stack = new Stack<>();
while (!stack.isEmpty() || head != null){
if (head != null){
stack.push(head);
head = head.left;
}else {
head = stack.pop();
if (head.value <= preValue){
return false;
}else {
preValue = head.value;
}
head = head.right;
}
}
}
return true;
}
判断一棵二叉树是否是完全二叉树
思路:
使用宽度优先算法,并满足一下条件:
- 任意节点,只有右孩子节点,没有左孩子节点,肯定不是完全二叉树,返回false
- 在1.成立的情况下,如果遇到了第一个左右孩子节点不全的节点,则后续所有节点都是叶子结点
//判断是否为完全二叉树
public static boolean isCBT(Node head){
if (head == null) {
return true;
}
Queue<Node> queue = new LinkedList<>();
//是否遇到过左右孩子不双全的节点
boolean leaf = false;
Node l = null;
Node r = null;
queue.offer(head);
while (!queue.isEmpty()){
head = queue.poll();
l = head.left;
r = head.right;
if (
//遇到过俩孩子不双全的情况,且当前节点有孩子节点
(leaf && (l != null || r != null))
||
//有右无左
(l == null && r != null)){
return false;
}
if (l != null){
queue.offer(l);
}
if (r != null){
queue.offer(r);
}
if(l == null || r ==null) {
leaf = true;
}
}
return true;
}
判断一棵二叉树是否是满二叉树
一种做法:统计最大深度L,再统计节点个数N,满足N = 2的L次方-1,则是满二叉树,否则不是
判断一棵二叉树是否是平衡二叉树
平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
具体代码看下面递归套路
二叉树的递归套路(解决树形DP问题)
平衡二叉树的判断
public static class ReturnType{
public boolean isBalanced;
public int height;
public ReturnType(boolean isB,int hei){
isBalanced = isB;
height = hei;
}
}
public static boolean isBalanced(Node head){
return process(head).isBalanced;
}
public static ReturnType process(Node x){
if(x == null){
return new ReturnType(true,0);
}
ReturnType leftData = process(x.left);
ReturnType rightData= process(x.right);
int height = Math.max(leftData.height,rightData.height) + 1;
boolean isBalanced = leftData.isBalanced &&
rightData.isBalanced &&
Math.abs(leftData.height,rightData.height) < 2;
return new ReturnType(isBalanced, height);
}
分析题目,需要子树提供什么信息,就建立对应的返回类型
搜索二叉树的判断
//套路解
static class ReturnBSTData{
boolean isBST;
int min;
int max;
public ReturnBSTData(boolean isCBT, int min, int max) {
this.isBST = isBST;
this.min = min;
this.max = max;
}
}
static boolean BST(Node head) {
if (head==null)
return true;
return process(head).isBST;
}
static ReturnBSTData process(Node x) {
if (x == null)
return null;
ReturnBSTData left = process(x.left);
ReturnBSTData right = process(x.right);
int min = x.value;
int max = x.value;
//从全局考虑,min是左子树和右子树和当前节点的最小值
//因为左右子树可能有空值
//max是左子树和右子树和当前节点的最大值
if (left != null){
min = Math.min(min,left.min);
max = Math.max(max,left.max);
}
if (right != null){
min = Math.min(min,right.min);
max = Math.max(max,right.max);
}
boolean isBST = true;
//左子树不为空,则会有返回值,
//这时候就得看看左子树是否是搜索二叉树
//或者左子树的最大值和当前节点比较一下
if (left != null && (!left.isBST || x.value <= left.max))
isBST = false;
//右子树不为空,则会有返回值,
//这时候就得看看右子树是否是搜索二叉树
//或者右子树的最小值和当前节点比较一下
if (right != null && (!right.isBST || x.value >= right.min))
isBST = false;
return new ReturnBSTData(isBST,min,max);
}
此套路适合用来解决一切树型DP问题
满二叉树的判断
public static class Info{
private int height;
private int nodes;
public Info(int height, int nodes) {
this.height = height;
this.nodes= nodes;
}
}
private static boolean isFull(Node head){
if (null == head){
return true;
}
Info res = process(head);
//这边需要注意,一定要加括号,位运算符<<优先级比-低
return res.nodes == (1 << res.height)-1;
}
private static Info process(Node x){
if (x == null){
return new Info(0, 0);
}
Info leftData = process(x.left);
Info rightData = process(x.right);
int height = Math.max(leftData.height,rightData.height) + 1;
int nodes = leftData.nodes + rightData.nodes + 1;
return new Info(height, nodes);
}
给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点
D和F的最低公共祖先就是B
E和F的最低公共祖先就是E
//o1,o2一定属于head为头的树
//返回o1,o2的最低公共祖先
public static Node lca(Node head,Node o1,Node o2){
HashMap<Node,Node> fatherMap = new HashMap<>();
fatherMap.put(head,head);
process(head,fatherMap);
HashSet<Node> set1 = new HashSet<>();
set1.add(o1);
Node cur = o1;
//set存了o1往上的所有父节点
while(cur != fatherMap.get(cur) ){
set1.add(cur);
cur = fatherMap.get(cur);
}
set1.add(head);
cur = o2;
//遍历o2的父节点,第一个在set里,则是最低公共祖先
while( !set1.contains(cur) ){
cur = fatherMap.get(cur);
}
return cur;
}
//递归的目的是找到每个节点的父节点,除了head节点
public static void process(Node head,
HashMap<Node,Node> fatherMap){
if(head == null){
return;
}
fatherMap.put(head.left,head);
fatherMap.put(head.right,head);
process(head.left, fatherMap);
process(head.right, fatherMap);
}
//另一种做法
public static Node lowestAncestor(Node head,Node o1,Node o2){
if(head == null || head == o1 || head == o2){
return head;
}
Node left = lowestAncestor(head.left,o1,o2);
Node right= lowestAncestor(head.right,o1,o2);
if(left != null && right != null){
return head;
}
//左右两棵树,并不都有返回值
return left != null ? left : right;
}
解法二解析:
在二叉树中找到一个节点的后继节点
现在有一种新的二叉树节点类型如下
public class Node{
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int val){
value = val;
}
}
该结构比普通二叉树节点结构多了一个指向父节点的parent节点
假设有一颗Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头结点的parent指向null
只给一个在二叉树的某个节点node,请实现返回node的后继节点的函数
在二叉树的中序遍历中,node的下一个节点叫做node的后继节点
时间复杂度o(k)
public static Node getSuccessorNode(Node node){
if(node == null){
return node;
}
if(node.right != null{
//返回右树的最左节点
return getLeftMost(node.right);
}else{ //无右树
Node parent = node.parent;
//当前节点是其父节点的右孩子
while(parent != null && parent.left != null){
node = parent;
parent = node.parent;
}
return parent;
}
}
//找到根为当前节点的树的最左节点
public static Node getLeftMost(Node node){
if(node == null){
return node;
}
while(node.left != null){
node = node.left;
}
return node;
}
二叉树的序列化和反序列化
就是内存里的一颗树如何变成字符串形式,又如何从字符串变成内存里的树
如何判断一颗二叉树是不是另一颗二叉树的子树?
使用前序方式序列化和反序列化
//以head开头的树,请序列化成字符串返回
public static String serialByPre(Node head){
if(head == null){
return "#_";
}
String res = head.value + "_";
res += serialByPre(head.left);
res += serialByPre(head.right);
return res;
}
public static Node reconByPreString(String preStr){
String[] values = preStr.split("_");
Queue<String> queue = new LinkedList<>();
for(int i = 0; i != values.length;i++){
queue.add(values[i]);
}
return reconPreOrder(queue);
}
public static Node reconPreOrder(Queue<String> queue){
String value = queue.poll();
if(value.equal("#"){
return null;
}
Node head = new Node(Integer.valueOf(value));
head.left = reconPreOrder(queue);
head.right = reconPreOrder(queue);
return head;
}
折纸问题(微软原题)
所以实际就是二叉树的中序遍历
这是一颗头结点是凹折痕,左子树的头结点为凹折痕,右子树的头结点为凸折痕的二叉树
public static void printAllFolds(int N){
printProcess(1,N,true);
}
//递归过程,来到某一节点
//i的节点的尾数,N为一共的层数,down == true 凹 down == false 凸
public static void printProcess(int i,int N,boolean down){
if(i > N){
return;
}
printProcess(i + 1,N,true);
System.out.println(down ? "凹" : "凸");
printProcess(i + 1,N,false);
}