目录
五.如何完成二叉树的宽度优先遍历(常见题目:求一颗二叉树的宽度)
七.给定两个二叉树节点node1和node2,找到他们的最低公共祖先节点
一.二叉树节点结构
public static class Node{
int value;
Node left;
Node right;
}
二.用递归方式实现二叉树的先序、中序、后序遍历
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.print("pre-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
stack.add(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();
}
先序遍历:
- 从栈中弹出一个节点cur
- 打印(处理)cur
- 先右再左(如果有)
- 重复1-3直到栈空
public static void inOrderUnRecur(Node head) {
System.out.print("in-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
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 posOrderUnRecur1(Node head) {
System.out.print("pos-order: ");
if (head != null) {
Stack<Node> s1 = new Stack<Node>();
Stack<Node> s2 = new Stack<Node>();
s1.push(head);
while (!s1.isEmpty()) {
head = s1.pop();
s2.push(head);
if (head.left != null) {
s1.push(head.left);
}
if (head.right != null) {
s1.push(head.right);
}
}
while (!s2.isEmpty()) {
System.out.print(s2.pop().value + " ");
}
}
System.out.println();
}
public static void posOrderUnRecur2(Node h) {
System.out.print("pos-order: ");
if (h != null) {
Stack<Node> stack = new Stack<Node>();
stack.push(h);
Node c = null;
while (!stack.isEmpty()) {
c = stack.peek();
if (c.left != null && h != c.left && h != c.right) {
stack.push(c.left);
} else if (c.right != null && h != c.right) {
stack.push(c.right);
} else {
System.out.print(stack.pop().value + " ");
h = c;
}
}
}
System.out.println();
}
后序遍历:
- 弹出栈,cur
- cur放入收集栈stack2
- 先左再右压入stack1
为什么可以实现后序?
因为stack1的弹出顺序为:头右左 => 放入收集栈stack2然后一起弹出 => 左右头
直接实现左右头比较难,考虑实现头右左,然后通过另外一个收集栈收集起来反向打印。
四.如何直观打印一颗二叉树
package class05;
public class Code02_PrintBinaryTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static void printTree(Node head) {
System.out.println("Binary Tree:");
printInOrder(head, 0, "H", 17);
System.out.println();
}
public static void printInOrder(Node head, int height, String to, int len) {
if (head == null) {
return;
}
printInOrder(head.right, height + 1, "v", len);
String val = to + head.value + to;
int lenM = val.length();
int lenL = (len - lenM) / 2;
int lenR = len - lenM - lenL;
val = getSpace(lenL) + val + getSpace(lenR);
System.out.println(getSpace(height * len) + val);
printInOrder(head.left, height + 1, "^", len);
}
public static String getSpace(int num) {
String space = " ";
StringBuffer buf = new StringBuffer("");
for (int i = 0; i < num; i++) {
buf.append(space);
}
return buf.toString();
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(-222222222);
head.right = new Node(3);
head.left.left = new Node(Integer.MIN_VALUE);
head.right.left = new Node(55555555);
head.right.right = new Node(66);
head.left.left.right = new Node(777);
printTree(head);
head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.right.left = new Node(5);
head.right.right = new Node(6);
head.left.left.right = new Node(7);
printTree(head);
head = new Node(1);
head.left = new Node(1);
head.right = new Node(1);
head.left.left = new Node(1);
head.right.left = new Node(1);
head.right.right = new Node(1);
head.left.left.right = new Node(1);
printTree(head);
}
}
五.如何完成二叉树的宽度优先遍历(常见题目:求一颗二叉树的宽度)
1.宽度优先搜索(层次遍历)
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);
}
}
}
2.求一棵二叉树的最大宽度
如下图,这个棵树的第三层节点最多,所以这棵树最大宽度是3。
这题其实就是求每层的节点个数,因此除了简单层序遍历外,还应该可以清晰辨别每个节点所在的层数。
=> 这里可以借助一个HashMap来记录每个节点对应的层数 => HashMap<Node, Integer>
public static int getMaxWidth(Node head) {
if(head==null)return 0;
Queue<Node> queue=new LinkedList<>();
queue.add(head);
HashMap<Node, Integer> hashmap=new HashMap<>();
hashmap.put(head, 1);
int max=0;
int curLevel=0;
int curLevelNode=0;
while(!queue.isEmpty()) {
Node cur=queue.poll();
int curNodeLevel=hashmap.get(cur);
if(curNodeLevel==curLevel) {
curLevelNode++;
}
else {
curLevel++;
max=max<curLevelNode?curLevelNode:max;
curLevelNode=1;
}
if(cur.left!=null) {
hashmap.put(cur.left, curNodeLevel+1);
queue.add(cur.left);
}
if(cur.right!=null) {
hashmap.put(cur, curNodeLevel+1);
queue.add(cur.right);
}
}
return max;
}
也可以不用哈希表,通过维护三个变量也可以完成。
当前层的最后一个节点curend
下一层的最后一个节点nextend
当前层的节点数量curLevelNode
利用了curend判断有没有到当前层的最后一个节点,同时在当前层节点的孩子节点从左到右入队的过程中将nextend不断更新,当遍历到当前层的最后一个节点时,恰好也完成了nextend的更新。利用curend和nextend的滚动更新,可以实现标记每层的终止节点,从而可以正确地统计同一层的节点数量。
public static int getMaxWidth2(Node head) {
if(head==null)return 0;
Node curend=head;
Node nextend=null;
int curLevelNode=0;
int max=0;
Queue<Node> queue=new LinkedList<>();
queue.add(head);
while(!queue.isEmpty()) {
Node cur=queue.poll();
if(cur.left!=null) {
queue.add(cur.left);
nextend=cur.left;
}
if(cur.right!=null) {
queue.add(cur.right);
nextend=cur.right;
}
curLevelNode++;
if(cur==curend) {
curend=nextend;
max=max<curLevelNode?curLevelNode:max;
curLevelNode=0;
}
}
return max;
}
六.二叉树的相关概念及其实现判断
只要是树形DP(我可以向左树和右树要信息来解决的问题)就可以用递归套路解决。
1.如何判断一棵二叉树是搜索二叉树
中序遍历的结果是依次升序就是搜索二叉树,否则不是。
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); //右树如果是整棵树就是BST,如果不是整棵树就不是BST
}
public static boolean inorder(Node head) {
if(head==null)return true;
int preValue=Integer.MIN_VALUE;
Stack<Node> ss=new Stack<>();
while(!ss.empty() || head!=null) {
if(head!=null) {
ss.push(head);
head=head.left;
}
else {
head=ss.pop();
if(preValue>=head.value)return false;
else preValue=head.value;
head=head.right;
}
}
return true;
}
递归套路:我需要向左树和右树要什么信息,然后建立递归。
public static class ReturnType1{
public boolean isBST;
public int max;
public int min;
public ReturnType1(boolean x, int y, int z) {
isBST=x;
max=y;
min=z;
}
}
public static boolean process1(Node head) {
return process2(head).isBST;
}
public static ReturnType1 process2(Node x) {
if(x==null)return null;
ReturnType1 leftData=process2(x.left);
ReturnType1 rightData=process2(x.right);
int min=x.value;
int max=x.value;
if(leftData!=null) {
min=Math.min(min, leftData.min);
max=Math.max(max, leftData.max);
}
if(rightData!=null) {
min=Math.min(min, leftData.min);
max=Math.max(max, rightData.max);
}
boolean isBST=true;
if(leftData!=null&&(!leftData.isBST||leftData.max>=x.value)) {
isBST=false;
}
if(rightData!=null&&(!rightData.isBST||x.value>=rightData.min)) {
isBST=false;
}
return new ReturnType1(isBST, max, min);
}
2.如何判断一棵二叉树是完全二叉树
两个条件:
- 任一节点如果出现有右无左,则返回false;
- 在条件一不违规下,如果遇到了第一个左右孩子不全的节点,那么从该节点往后的所有节点都必须是叶子节点。
public static boolean isCBT(Node head) {
if (head == null) {
return true;
}
LinkedList<Node> queue = new LinkedList<>();
boolean leaf = false;
Node l = null;
Node r = null;
queue.add(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.add(l);
}
if (r != null) {
queue.add(r);
}
if(l==null || r==null) {
leaf = true;
}
}
return true;
}
3.如何判断一棵二叉树是否是满二叉树
递归套路:
统计数上的节点数量N和数的深度h,判断是否满足N=2^h-1
public static class Info{
public int height;
public int nodes;
public Info(int h, int n) {
height=h;
nodes=n;
}
}
public static boolean isF(Node head) {
if(head==null)return true;
Info tmp=process3(head);
return Math.pow(2, tmp.height)-1==tmp.nodes;
}
public static Info process3(Node x) {
if(x==null)return new Info(0,0);
Info leftData=process3(x.left);
Info rightData=process3(x.right);
int height=Math.max(leftData.height, rightData.height)+1;
int nodes=leftData.nodes+rightData.nodes+1;
return new Info(height, nodes);
}
4.如何判断一颗二叉树是平衡二叉树
满足条件:
- 左树是平衡二叉树
- 右树的平衡二叉树
- |左树高-右树高|<=1
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isBalanced(Node head) {
return process(head).isBalanced;
}
public static class ReturnType {
public boolean isBalanced;
public int height;
public ReturnType(boolean isB, int hei) {
isBalanced = isB;
height = hei;
}
}
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);
boolean isBalanced = leftData.isBalanced && rightData.isBalanced
&& Math.abs(leftData.height - rightData.height) < 2;
return new ReturnType(isBalanced, height);
}
七.给定两个二叉树节点node1和node2,找到他们的最低公共祖先节点
情况一:O1是O2的LCA 或 O2是O1的LCA
情况二:O1与O2不互为LCA,向上才找到的LCA
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 static 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的后继节点
两种情况:
情况一:x节点有右树的时候,x右树上的最左节点就是x的后继
情况二:x节点没有右树的时候,x往上找到的(第一个节点是其父亲的左孩子 的那个节点 的父亲)就是x的后继节点 【因为x节点一定是那个父亲节点左树上最后一个打印的】
PS:情况二有可能找不到后继节点,此时就让其后继为null
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 != node) {
node = parent;
parent = node.parent;
}
return parent;
}
}
九.二叉树的序列化和反序列化
就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树。如何判断一颗二叉树是不是另一颗二叉树的子树?
先序遍历序列化:先序遍历,遇到节点记录节点值,遇到空就用'#'来表示。
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<String>();
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.equals("#")) {
return null;
}
Node head = new Node(Integer.valueOf(value));
head.left = reconPreOrder(queue);
head.right = reconPreOrder(queue);
return head;
}
十.折横问题
一张纸条对折n次,请打印出从左到右的折横状况。
这是头节点为凹,每个左子树头节点的为凹,右子树头节点为凸的二叉树。 对这棵树中序遍历打印就是从左到右的折横情况。
public static void printAllFolds(int N) {
printProcess(1, N, true);
}
public static void printProcess(int i, int N, boolean down) {
if (i > N) {
return;
}
printProcess(i + 1, N, true);
System.out.println(down ? "down " : "up ");
printProcess(i + 1, N, false);
}
public static void main(String[] args) {
int N = 1;
printAllFolds(N);
}