二叉树的遍历
先序遍历
递归版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void preorder(Node head) {
if (head == null) { return; } Visit(head);
preorder(head.left);
preorder(head.right); }
|
非递归版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public void preOrder(Node head) { if (head != null) { Stack<Node> stack = new Stack<>(); stack.add(head); while (!stack.isEmpty()) { head = stack.pop(); Visit(head); if (head.right != null) { stack.push(head.right); } if (head.left != null) { stack.push(head.left); } } } }
|
中序遍历
递归版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void inorder(Node head) {
if (head == null) { return; }
inorder(head.left);
inorder(head.right); }
|
非递归版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public List<Node> inorder(Node head) { List<Node> ans = new ArrayList<>(); Stack<Node> stack = new Stack<>(); if (head == null) { return ans; } while (!stack.isEmpty() || head != null) { while (head != null) { stack.push(head); head = head.left; } if (!stack.isEmpty()) { head = stack.pop(); ans.add(head); head = head.right; } } return ans; }
|
后序遍历
递归版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void postorder(Node head) { if (head == null) { return; } postorder(head.left); postorder(head.right); }
|
非递归版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
public void postorder(Node head) {
if (head != null) { Stack<Node> in = new Stack<>(); Stack<Node> out = new Stack<>();
in.push(head);
while (!in.isEmpty()) {
head = in.pop(); out.push(head);
if (head.left != null) { in.push(head.left); }
if (head.right != null) { in.push(head.right); } }
while (!out.isEmpty()) { out.pop(); } } }
|
二叉树宽度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public int getBinaryTreeWidth(Node head) {
int maxsize = 100; Node sentinel; Node[] que = new Node[maxsize]; int front = 0, rear = 0; int max = 0; int count = 0; int last = 1;
rear = (rear + 1) % maxsize; que[rear] = head;
while (front != rear) { front = (front + 1) % maxsize; sentinel = que[front];
count++;
if (sentinel.left != null) { rear = (rear +1) % maxsize; que[rear] = sentinel.left; }
if (sentinel.right != null) { rear = (rear +1) % maxsize; que[rear] = sentinel.right; }
if (front == last) { last = rear; max = count > max ? count : max; count = 0; } } return max; }
|
二叉搜索树
判断方式1 遍历中序序列
算法思想
二叉搜索树中序遍历序列呈递增特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public boolean isBST(Node head) { if (head == null) { return false; }
List<Node> inList = new LinkedList<>();
process(head, inList);
int pre = Integer.MIN_VALUE;
for (Node node: inList) { if (pre >= node.val) { return false; } pre = node.val; } return true; }
public void process(Node node, List<Node> inList) { if (node == null) { return; } process(node.left, inList); inList.add(node); process(node.right, inList); }
|
判断方式2 常规递归检查
算法思想
判断是否为BST,主要是看BST性质:对于其下的任意一颗子树,其左孩子节点的值小于其根节点的值,其根节点的值小于其右孩子的值,因此用递归解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| int prev = Integer.MIN_VALUE;
public boolean isBST(Node head) { if (head == null) { return true; } boolean isLeftBST = isBST(head.left);
if (!isLeftBST) { return false; } if (head.val <= prev) { return false; } else { prev = head.val; } return isBST(head.right); }
|
判断方式3 树型DP套路
树型DP使用场景
如果题目求解目标 $S$ 规则,其求解流程定成以每一个节点为头结点的子树在 $S$ 下的每个答案,并且最终答案一定在其中
算法思想
- 判断BST,以X为根节点的BST满足:左子树为BST,右子树为BST,对应左子树的 $maxX$
- 因此左子树需要的信息 (1.左子树是否为BST 2.左子树的Max)
- 因此右子树需要的信息 (1.右子树是否为BST 2.右子树的Min)
Step1
以某个节点X为头结点的子树中,分析答案的可能性,并且可能性是以X的左子树、X的右子树和X整棵树的角度考虑的
- X左子树是否为BST
- X右子树是否为BST
- 以X为根节点的树是否为BST
Step2
根据可能性,列出需要的信息
- 左子树是否为BST
- 左子树的最大值
- 右子树是否为BST
- 右子树的最小值
Step3
合并第二步信息,对左子树和右子树提出同样要求,写出信息结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ReturnData { public boolean isBST; public int min; public int max;
public ReturnData(boolean isBST, int min, int max) { this.isBST = isBST; this.min = min; this.max = max; }
}
|
Step4
设计递归函数,递归函数处理以X为头节点的情况下的答案,包括设计递归的basecase,默认直接得到左子树和右子树的所有信息,以及把可能性做出整合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public ReturnData process(Node head) {
if (head == null) { return null; }
ReturnData leftData = process(head.left); ReturnData rightData = process(head.right);
int min = head.val; int max = head.val;
if (leftData != null) { min = Math.min(min, leftData.min); max = Math.max(max, leftData.max); } if (rightData != null) { min = Math.min(min, rightData.min); max = Math.max(max, rightData.max); }
boolean isBST = true;
if (leftData != null && (!leftData.isBST || leftData.max >= head.val)) { isBST = false; } if (rightData != null && (!rightData.isBST || head.val >= rightData.min)) { isBST = false; }
return new ReturnData(isBST, max, min); }
|
1 2 3
| public boolean isBST(Node head) { return process(head).isBST; }
|
完全二叉树
算法思想
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public boolean isCBT(Node head) { if (head == null) { return true; }
LinkedList<Node> queue = new LinkedList<>();
boolean leaf = false;
Node lchild = null; Node rchild = null;
queue.add(head);
while (!queue.isEmpty()) { head = queue.poll(); lchild = head.left; rchild = head.right;
if (leaf && (lchild != null || rchild!= null) || (lchild == null && rchild != null)) { return false; } if (lchild != null) { queue.add(lchild); } if (rchild != null) { queue.add(rchild); } if (lchild == null || rchild == null) { leaf = true; } } return true; }
|
平衡二叉树
Step1
以 X
为头节点的子树中,分析答案的可能性。以X
的左子树、X
的右子树和X
整棵树角度分析可能性
X
的左子树是否平衡
X
的右子树是否平衡
X
为头结点的左右子树高度差
上述满足后,则平衡
Step2
根据可能性列出需要的信息,本题是要知道左子树是否平衡,右子树是否平衡,以及高度信息
Step3
整合信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class ReturnData {
public boolean isBalanced;
public int height;
public ReturnData(boolean isBalanced, int height) { this.isBalanced = isBalanced; this.height = height }
}
|
Step4
设计递归函数,处理以X为头节点的情况的答案,包括 base case,左右子树信息,以及可能性整合,返回信息结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public ReturnData process(Node x) {
if (x == null) { return new ReturnData(true, 0); }
ReturnData leftData = process(x.left);
ReturnData 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 ReturnData(isBalanced, height); }
|
调用函数
1 2 3 4
| public boolean isBalanced(Node head) { return process(head).isBalanced; }
|
满二叉树
树形dp
Step1
以x为头节点的子树中,分析答案的可能性,以x的左子树、x的右子树和x整棵树角度去分析可能性
Step2
根据可能性列出需要的信息,本题是要知道节点数量和树的高度
Step3
整合信息
1 2 3 4 5 6 7 8 9 10 11 12
| public class ReturnData{ public int height; public int nodes;
public ReturnData(int height, int nodes) { this.height = height; this.nodes = nodes; } }
|
Step4
设计递归函数,处理以x为头节点的情况的答案,包括base case,左右子树信息,以及可能性整合,返回信息结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public ReturnData process(Node x) { if (x == null) { return new ReturnData(0, 0); }
ReturnData leftData = process(x.left);
ReturnData rightData = process(x.right);
int height = Math.max(leftData.height, rightData.height) + 1;
int nodes = leftData.nodes + rightData.nodes + 1;
return new ReturnData(height, nodes); }
|
最终调用
1 2 3 4 5 6 7 8 9
| public boolean isFBT(Node head) { if (head == null) { return true; } ReturnData data = process(head); return data.nodes == (1 << data.height - 1); }
|
找一个节点中序遍历下的后继节点
1 2 3 4 5 6 7 8 9 10
| public class Node { public int value; public Node left; public Node right; public Node parent;
public Node(int data) { this.value = data; } }
|
情况1 node
有右子树
如果node
有右子树,那么后继节点就是右子树上最左边的节点
情况2 node
没有右子树
如果node
没有右子树, 先看node
是不是node
父节点的左孩子节点
- 如果
node
是其父节点的左孩子节点, 那么此时node
的父节点是node
的后继节点
- 如果
node
是其父节点的右孩子节点, 就向上寻找node
的后继节点, 假设向上移动到的节点为s
,s
的父节点是p
, 如果发现s
是p
的左孩子, 那么节点p
便是node
的后继节点,否则就一直向上移动
情况3
如果在情况2中一直寻找,都移动到空节点时还是没有发现node的后继节点,说明node根本不存在后继节点
getLeftMost
1 2 3 4 5 6 7 8 9 10 11 12
| public Node getLeftMost(Node node) { if (node == null) {
return node; } while (node.left != null) { node = node.left; } return node;
}
|
getNextNode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public Node getNextNode(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; } }
|
序列化问题
给出一个定义:
二叉树被记录成文件的过程叫序列化,通过文件内容重建原来的二叉树叫反序列化。给定一颗二叉树的头节点head,已知二叉树节点类型为32位整数,设计序列化和反序列化的方案
先序遍历
序列化方法
1 2 3 4 5 6 7 8 9
| public String serialByPre(Node head) { if (head == null) { return "#!"; } String res = head.value + "!"; res += serialByPre(head.left); res += serialByPre(head.right); return res; }
|
反序列化方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public Node reconPreString(String preStr) { String[] values = preStr.split("!");
Queue<String> queue = new LinkedList<>();
for (String val : queue) { queue.offer(val); }
return reconPreOrder(queue); }
public 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; }
|
层次遍历
序列化方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public String serialByLevel(Node head) {
if (head == null) { return "#!"; }
String res = head.val + "!"; Queue<Node> queue = new LinkedList<>(); 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; }
|
反序列化方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public Node reconByLevelString(String levelStr) {
String[] values = levelStr.split("!"); int index = 0; Node head = generateNodeByString(values[index++]); Queue<Node> queue = new LinkedList<>();
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)); }
|
最近公共祖先
给定一颗二叉树的头节点head
,以及这棵树中的两个节点o1
和o2
,请返回o1
和o2
的最近公共祖先
原始问题
算法思想
后序遍历二叉树,当前节点记为cur,假设处理cur左子树返回节点为left,处理右子树时返回节点为right
如果发现cur
等于null,或者o1
、o2
,则返回cur
如果left
和right
都为空,说明cur
整棵子树没有发现过o1
或o2
,返回null
如果left
和right
都不为空,说明左子树发现过o1
或o2
,右子树也发现过o1
或o2
,说明o1
向上与o2
向上的过程中,首次在cur
相遇,返回cur
如果left
和right
有一个为空,另一个不为空,假设不为空的那个记为node
,此时node
要么是o1
或o2
的一个,要么是o1
和o2
的最近公共祖先,直接返回node
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public 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; }
|
进阶问题
如果查询操作十分频繁,请想办法让单条查询的查询时间减少。
结构1:原始结构
建立二叉树中每个节点对应的父结点信息 <子结点,父结点>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class Record { private HashMap<Node, Node> map;
public Record(Node head) { map = new HashMap<>(); if (head != null) { map.put(head, null); } setMap(head); }
public void setMap(Node x) { if (x == null) { return; } if (x.left != null) { map.put(x.left, x); } if (x.right != null) { map.put(x.right, x); } setMap(x.left); setMap(x.right); }
public Node query(Node o1, Node o2) { HashSet<Node> path = new HashSet<>();
while (map.containsKey(o1)) { path.add(o1); o1 = map.get(o1); } while (!path.contains(o2)) { o2 = map.get(o2); } return o2; } }
|
复杂度分析
结构1建立记录的过程时间复杂度为 $O(N)$、额外空间复杂度为 $O(N)$,进行查询操作时,时间复杂度为 $O(h)$,其中,$h$ 为二叉树的高度。
结构2:优化结构
直接建立任意两个子结点之间的最近公共祖先记录,加速查询
建立过程:
1.对二叉树中的每棵子树(一共N棵)都进行步骤2
2.假设子树的头结点为h
h
所有后代结点和h
结点的最近公共祖先都是h
,并记录下来。
h
的左子树的每个结点和h
右子树的每个结点的最近公共祖先都是h,并记录下来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
| public class Record { private HashMap<Node, HashMap<Node, Node>> map;
public Record(Node head) { map = new HashMap<>(); initMap(head); setMap(head); }
public void initMap(Node head) { if (head == null) { return; } map.put(head, new HashMap<>());
initMap(head.left); initMap(head.right); }
public void setMap(Node head) { if (head == null) { return; } headRecord(head.left, head); headRecord(head.right, head);
subRecord(head);
setMap(head.left); setMap(head.right); }
public void headRecord(Node n, Node h) { if (n == null) { return; } map.get(n).put(h, h);
headRecord(n.left, h); headRecord(n.right, h); }
public void subRecord(Node head) { if (head == null) { return; } preLeft(head.left, head.right, head);
subRecord(head.left); subRecord(head.right); }
public void preLeft(Node l, Node r, Node h) { if (l == null) { return; } preRight(l, r, h);
preLeft(l.left, r, h); preLeft(l.right, r, h); }
public void preRight(Node l, Node r, Node h) { if (r == null) { return; } map.get(l).put(r, h);
preRight(l, r.left, h); preRight(l, r.right, h); }
public Node query(Node o1, Node o2) { if (o1 == o2) { return o1; } if (map.containsKey(o1)) { return map.get(o1).get(o2); } if (map.containsKey(o2)) { return map.get(o2).get(o1); } return null; } }
|