简述
递归序(递归访问)
- 递归:访问次序
递归序:1,2,4,4,4,2,5,5,5,2,1,3,6,6,6,3,7,7,7,3,1 先序:第一次在递归序出现就打印,第二,三次访问时无操作:1,2,4,5,3,6,7 中序:第二次在递归序出现就打印,第一,三次访问时无操作:4,2,5,1,6,3,7 后序:第三次在递归序出现就打印,第一,二次访问时无操作:4,5,2,6,7,3,1
public static void preOrderRecur(Node head){
if (head == null){
return;
}
System.out.println(head.value + " ");
preOrderRecur(head.left);
preOrderRecur(head.right);
}
public static void inOrderRecur(Node head){
if (head == null){
return;
}
inOrderRecur(head.left);
System.out.println(head.value + " ");
inOrderRecur(head.right);
}
public static void posOrderRecur(Node head){
if (head == null){
return;
}
posOrderRecur(head.left);
posOrderRecur(head.right);
System.out.println(head.value + " ");
}
非递归访问
- 非递归:通过手写栈实现
1 先序: (1)从栈中弹出一个结点 (2)打印(处理)该结点 (3)先右后左(如果有)压入栈中(保证出栈是左,右) (4)重复,直至栈为空
public static void preOrderRecur(Node head){
if (head != null){
Stack<Node> stack = new Stack<>();
stack.push(head);
while (!stack.isEmpty()){
Node p = stack.pop();
System.out.println(p.value);
if (p.right != null){
stack.push(p.right);
}
if (p.left != null){
stack.push(p.left);
}
}
}
}
2 中序: (1)每颗子树的左边界进栈 (2)依次弹出的过程中打印(处理),弹出结点的右子树(及其左边界)进栈 (3)重复1,2
public static void inOrderRecur(Node head){
if (head != null){
Stack<Node> stack = new Stack<Node>();
Node p = head;
while (!stack.isEmpty() || p != null){
if (p != null){
stack.push(p);
p = p.left;
}else {
p = stack.pop();
System.out.println(p.value + " ");
p = p.right;
}
}
}
}
3 后序:需要一个收集栈 (1)从栈中弹出一个结点 (2)将该结点放入收集栈(父节点在收集栈底) (3)先左后右(如果有)压入栈中(保证收集栈里是右,左) (4)重复,直至栈为空 (5)打印(处理)收集栈(出栈是左,右,父)
public static void preOrderRecur(Node head){
if (head != null){
Stack<Node> stack = new Stack<>();
Stack<Node> temp = new Stack<>();
stack.push(head);
while (!stack.isEmpty()){
Node p = stack.pop();
temp.push(p);
if (p.left != null){
stack.push(p.left);
}
if (p.right != null){
stack.push(p.right);
}
}
while (!temp.isEmpty()){
Node p = temp.pop();
System.out.println(p.value + " ");
}
}
}
直观打印树
public static class Node{
int value;
Node left;
Node right;
public Node(int date){
this.value = date;
}
}
public static void printTree(Node head){
System.out.println("Binary Tree");
printInOrder(head, 0, "H", 12);
System.out.println();
}
public static void printInOrder(Node head, int height, String to, int len){
if (head == null){
return;
}
printInOrder(head.right, height + 1, "↙", 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 = " ";
StringBuilder buf = new StringBuilder(" ");
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(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.left.right = new Node(5);
head.right.left = new Node(6);
head.right.right = new Node(7);
head.left.right.right = new Node(8);
head.right.right.left = new Node(9);
printTree(head);
}
理想图:
效果图:H:头结点;↖:表示父节点在其左上;↙:表示父节点在其左下
题目一:求树的最大宽度(广度优先)
层次遍历
public static void levelOrder(Node head){
if (head == null){
return;
}
Queue<Node> queue = new LinkedList<Node>();
Node p = head;
queue.add(p);
while (!queue.isEmpty()){
p = queue.poll();
if (p.left != null){
queue.add(p.left);
}
if (p.right != null){
queue.add(p.right);
}
}
}
- 笔试代码:
(1)思路:三个值,当前结点所在层,每层中的结点个数,树中结点最多的层 (2)实现方法:结合层次遍历,使用HashMap使得每个结点与对应的层绑定。出队时,将对应层数的结点加一,最后找到结点最多的层即可
public static int levelMax(Node head){
if (head == null){
return 0;
}
Queue<Node> queue = new LinkedList<Node>();
int level = 1;
int levelNum = 0;
int nodeMax = 0;
HashMap<Node, Integer> map = new HashMap<>();
Node p = head;
map.put(p, 1);
queue.add(p);
while (!queue.isEmpty()){
p = queue.poll();
if (map.get(p) == level){
levelNum++;
}else {
nodeMax = Math.max(nodeMax, levelNum);
level++;
levelNum = 1;
}
if (p.left != null){
map.put(p.left, level + 1);
queue.add(p.left);
}
if (p.right != null){
map.put(p.right, level + 1);
queue.add(p.right);
}
}
return nodeMax;
}
- 面试思路:
(1)思路:四个值,当前层的结束结点,当前层下一次的结束结点,每层中的结点个数,树中结点最多的层 (2)实现方式:结合层次遍历,第一层,该层的结束结点就是head,到了第二层将在第一层记录的下一层结束结点作为当前层的结束结点,在出,入对的同时更新下一次的结束结点,同时记录每层中的结点个数。
public static int levelMax(Node head){
if (head == null){
return 0;
}
Queue<Node> queue = new LinkedList<Node>();
Node levelEnd = head;
Node levelNextEnd = null;
int levelNum = 0;
int nodeMax = 0;
Node p = head;
queue.add(p);
while (!queue.isEmpty()){
p = queue.poll();
if (p.left != null){
levelNextEnd = p.left;
queue.add(p.left);
levelNum++;
}
if (p.right != null){
levelNextEnd =p.right;
queue.add(p.right);
levelNum++;
}
if (p == levelEnd){
levelEnd = levelNextEnd;
nodeMax = Math.max(levelNum, nodeMax);
levelNum = 0;
levelNextEnd = null;
}
}
return nodeMax;
}
题目二:二叉树相关概念判断
1. 如何判断是否为搜索二叉树(排序二叉树)
思路:中序遍历二叉树得到的序列,若为升序序列则为搜索二叉树
递归中序:
public static void inOrderRecur(Node head){
if (head == null){
return;
}
inOrderRecur(head.left);
System.out.println(head.value + " ");
inOrderRecur(head.right);
}
代码修改:
public static int preValue = Integer.MIN_VALUE;
public static boolean checkBST(Node head){
if (head == null){
return true;
}
if (!checkBST(head.left)){
return false;
}
if (head.value <= preValue){
return false;
}else {
preValue = head.value;
}
return checkBST(head.right);
}
非递归中序:
public static void inOrderRecur(Node head){
if (head != null){
Stack<Node> stack = new Stack<Node>();
Node p = head;
while (!stack.isEmpty() || p != null){
if (p != null){
stack.push(p);
p = p.left;
}else {
p = stack.pop();
System.out.println(p.value + " ");
p = p.right;
}
}
}
}
代码修改:
public static boolean checkBST(Node head){
if (head != null){
Stack<Node> stack = new Stack<Node>();
Node p = head;
int preValue = Integer.MIN_VALUE;
while (!stack.isEmpty() || p != null){
if (p != null){
stack.push(p);
p = p.left;
}else {
p = stack.pop();
if (p.value <= preValue){
return false;
}else {
preValue = p.value;
}
p = p.right;
}
}
}
return true;
}
总结:两种均为动态检查,也可先将二叉树的序列存入list类的集合中,在判断序列是否有序
2. 如何判断是否为完全二叉树
思路:广度优先(层次遍历),判断1:只要有结点是有右孩子,而无左孩子就返回false;判断2:从第一个子树不完整的结点开始,其后结点都要为叶子结点
public static boolean isCBT(Node head){
if (head == null){
return true;
}
Queue<Node> queue = new LinkedList<Node>();
boolean flag = false;
Node p = head;
queue.add(p);
while (!queue.isEmpty()){
p = queue.poll();
if ((p.left == null && p.right != null) || flag && (p.left != null && p.right != null)){
return false;
}
if (p.left != null){
queue.add(p.left);
}
if (p.right != null){
queue.add(p.right);
}
if (p.left == null || p.right == null){
flag = true;
}
}
return true;
}
3. 如何判断是否为满二叉树
思路:统计二叉树的深度L,节点数为N,若N = 2 ^ L - 1,则为满二叉树 方法:递归调用,求左子树的L,N;求右子树的L,N;求该树的L,N
public static boolean isFull(Node head){
ReturnType returnType = process(head);
return returnType.num == (1 << returnType.height - 1);
}
public static class ReturnType{
public int height;
public int num;
public ReturnType(int height, int num){
this.height = height;
this.num = num;
}
}
public static ReturnType process(Node head){
if (head == null){
return new ReturnType(0, 0);
}
ReturnType leftTree = process(head.left);
ReturnType rightTree= process(head.right);
int height = Math.max(leftTree.height, rightTree.height) + 1;
int num = leftTree.num + rightTree.num + 1;
return new ReturnType(height, num);
}
4. 如何判断是否为平衡二叉树
思路:平衡二叉树条件:(1)左子树平衡,左高度;(2)右子树平衡,右高度;(3)|左高度 - 右高度| <= 1 结合递归实现
public static boolean isBalancedTree(Node head){
return process(head).isBalanced;
}
public static class ReturnType{
public boolean isBalanced;
public int height;
public ReturnType(boolean isBalanced,int height){
this.isBalanced = isBalanced;
this.height = height;
}
}
public static ReturnType process(Node head){
if (head == null){
return new ReturnType(true, 0);
}
ReturnType leftTree = process(head.left);
ReturnType rightTree= process(head.right);
int height = Math.max(leftTree.height, rightTree.height) + 1;
boolean isBalanced = leftTree.isBalanced && rightTree.isBalanced
&& Math.abs(leftTree.height - rightTree.height) < 2;
return new ReturnType(isBalanced, height);
}
5. 二叉树递归套路(树型DP,动态规划)
思路:在判断二叉树是什么树时?分解为左子树是否满足,右子树是否满足,左右子树合并后是否满足(树型DP)
- 二叉树递归套路:判断是否为搜索二叉树
(1)左树搜索,左的max,min;(2)右树搜索,右的min,max;(3)左max < head.value < 右min
public static boolean isBSTree(Node head){
return process(head).isBST;
}
public static class ReturnType{
public boolean isBST;
public int min;
public int max;
public ReturnType(boolean isBST,int min, int max){
this.isBST = isBST;
this.min = min;
this.max = max;
}
}
public static ReturnType process(Node head){
if (head == null){
return null;
}
ReturnType leftTree = process(head.left);
ReturnType rightTree= process(head.right);
int min = head.value;
int max = head.value;
if (leftTree != null){
min = Math.min(min, leftTree.min);
max = Math.max(max, leftTree.max);
}
if (rightTree != null){
min = Math.min(min, rightTree.min);
max = Math.max(max, rightTree.max);
}
boolean isBST = true;
if (leftTree != null && (leftTree.isBST || leftTree.max > head.value)){
isBST = false;
}
if (rightTree != null && (rightTree.isBST || rightTree.min < head.value)){
isBST = false;
}
return new ReturnType(isBST, min, max);
}
- 在判断满二叉树,平衡二叉树时都是此方法
题目三:俩节点最低公共祖先
- 笔试思路:
(1)使用HashMap建立,每个节点与其父节点的关系 (2)使用HashSet存储,node1所有的祖先节点 (3)依次找node2的祖先节点,并在node1的HashSet中寻找是否有相同的,当遇到第一个相同的便是最低公共祖先节点
public static Node lowCommonAncestor(Node head, Node node1, Node node2){
HashMap<Node, Node> fatherMap = new HashMap<Node,Node>();
fatherMap.put(head, head);
process(head, fatherMap);
HashSet<Node> setNode1 = new HashSet<Node>();
Node p = node1;
while (p != fatherMap.get(head)){
setNode1.add(p);
p = fatherMap.get(p);
}
p = node2;
while (p != fatherMap.get(head)){
if (setNode1.contains(p)){
return p;
}
p = fatherMap.get(p);
}
return null;
}
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);
}
- 面试思路:
情况分析: (1)node1是node2的祖先节点,或node2是node1的祖先节点 (2)node1与node2不互为祖先节点,需要向上遍历才能找到 方法:遍历树节点,碰到node1,node2返回对应节点,其余的节点碰到都只是返回空 情况一:
情况二:
public static Node lowCommonAncestor(Node head, Node node1, Node node2){
if (head == null || head == node1 || head == node2){
return head;
}
Node left = lowCommonAncestor(head.left, node1, node2);
Node right = lowCommonAncestor(head.right, node1, node2);
if (left != null && right != null){
return head;
}
return left != null ? left : right;
}
题目四:树的后继节点查找(含父亲节点)
后继节点:在中序遍历的序列中,node的下一个节点叫作node的后继节点 情况分析: (1)node有右子树,则后继节点就是右子树最左边的节点 (2)node无右子树,则后继结点就是看node是那个结点左子树最右边的结点(判断当前节点是否是其祖先节点的左子树)
public static Node getSuccessorNode(Node node){
if (node == null){
return node;
}
if (node.right != null){
return getLeftNode(node);
}
else {
Node parent = node.parent;
while (parent != null && parent.left != node){
node = parent;
parent = node.parent;
}
return parent;
}
}
public static Node getLeftNode(Node node){
if (node == null){
return node;
}
while (node.left != null){
node = node.left;
}
return node;
}
题目五:二叉树的序列化和反序列化
思路:使用 “" 表示相隔节点之间的表示方式;使用 "#” 表示空结点
先序序列:序列化
public static String serialByPre(Node head){
if (head == null){
return "#_";
}
String str = head.value + "_";
str += serialByPre(head.left);
str += serialByPre(head.right);
return str;
}
反序列化
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 height, int N, Boolean down){
if (height > N){
return;
}
printProcess(height + 1, N, true);
System.out.println(down ? "凹" : "凸");
printProcess(height + 1, N, false);
}
|