1. 用递归和非递归的方式实现二叉树的先序、中序、后序遍历
先序遍历
递归
public void preOrderRecur(TreeNode root){
if (root == null){
return;
}
System.out.println(root.val + " ");
preOrderRecur(root.left);
preOrderRecur(root.right);
}
非递归
- 从栈中弹出一个结点cur
- 打印,即访问该结点
- 先把cur的右孩子入栈,再将左孩子入栈
- 重复上述步骤
中序遍历
递归
public void inOrderRecur(TreeNode root){
if (root == null){
return;
}
inOrderRecur(root.left);
System.out.println(root.val + " ");
inOrderRecur(root.right);
}
递归
- 将子树的左孩子入栈
- 直到没有左孩子的时候,弹出结点root
- 判断root的右孩子是否为空
- 如果有右孩子结点,重复上面的过程,将这个结点的左孩子入栈
- 如果没有右孩子,继续弹栈
- 每次弹出的时候就打印结点,遇到空结点就会弹栈,只要有左孩子就压栈,没有左孩子时就到右孩子上去
public void inOrderUnRecur(TreeNode root){
if (root == null){
return;
}
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || root != null){
if (root != null){
stack.push(root);
root = root.left;
}else{
root = stack.pop();
System.out.println(root.val + " ");
root = root.right;
}
}
}
后序遍历
递归
public void postOrderRecur(TreeNode root){
if (root == null){
return;
}
postOrderRecur(root.left);
postOrderRecur(root.right);
System.out.println(root.val + " ");
}
非递归
先序遍历是中左右,后序遍历是左右中,调整一下顺序,使得遍历时为中右左,然后翻转就得到左右中
- 从栈中弹出一个结点cur
- cur放入收集数组中
- 先将cur的左孩子入栈,再将右孩子入栈
- 重复上述步骤
- 翻转收集数组
也可以用一个栈来收集结果,由于栈是先进后出,就不用再翻转了
public void postOrderUnRecur(TreeNode root){
if (root == null){
return;
}
Stack<TreeNode> stack = new Stack<>();
List<TreeNode> list = new ArrayList<>();
stack.push(root);
while (!stack.isEmpty()){
TreeNode cur = stack.pop();
list.add(cur);
if (cur.left != null){
stack.push(cur.left);
}
if (cur.right != null){
stack.push(cur.right);
}
}
Collections.reverse(list);
list.forEach(node -> {
System.out.println(node.val);
});
}
2. 二叉树的最大宽度
为二叉树的结点编号
可以得到每一层的结点数量,第一个和最后一个结点的编号差值,就是每一层的宽度
public int getMaxWidth(TreeNode root){
if (root == null){
return 0;
}
LinkedList<TreeNode> queue = new LinkedList<>();
HashMap<TreeNode, Integer> map = new HashMap<>();
int ans = Integer.MIN_VALUE;
queue.add(root);
map.put(root, 0);
while (!queue.isEmpty()){
int size = queue.size();//每一层的结点数量
int width = map.get(queue.get(size - 1)) - map.get(queue.get(0)) + 1;
ans = Math.max(ans, width);
while (size > 0){
TreeNode cur = queue.pop();
if (cur.left != null){
queue.add(cur.left);
map.put(cur.left, map.get(cur) * 2 + 1);
}
if (cur.right != null){
queue.add(cur.right);
map.put(cur.right, map.get(cur) * 2 + 2);
}
size--;
}
}
return ans;
}
3. 判断一棵二叉树是否是搜索二叉树
递归
左子树上所有结点的值均小于它的根结点;右子树上所有结点均大于它的根结点。以中序遍历序列表示的时候是从小到大,所有结点的大小就是位于第一个结点和最后一个结点中间的。
isValidBST(TreeNode node, long lower, long upper)
表示以node为根的子树,它的结点是否在lower和upper的范围里。
public boolean isBST(TreeNode root){
return isValidBST(root, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
public boolean isValidBST(TreeNode node, long lower, long upper){
if (node == null){
return true;
}
//node的 大小越界
if (node.val <= lower || node.val >= upper){
return false;
}
//递归左子树时修改上界,表示左子树所有结点都小于它的根结点
//递归右子树同理
return isValidBST(node.left, lower, node.val) && isValidBST(node.right, node.val, upper);
}
中序遍历
搜索二叉树的中序序列是递增的,所以中序遍历的时候判断当前结点是否大于上一个结点
public int pre = Integer.MIN_VALUE;
public boolean isBST2(TreeNode root){
if (root == null){
return true;
}
//中序遍历
boolean left = isBST2(root.left);
//判断左子树是否是搜索二叉树
if (!left){
return false;
}
//如果中序遍历时当前子树的根结点小于上一个结点,就说明这不是搜索二叉树
if (root.val <= pre){
return false;
}else {
//左子树包括根结点构成搜索二叉树,更新pre
pre = root.val;
}//左子树是搜索二叉树
return isBST2(root.right);//左子树是搜索二叉树,当右子树也是搜索二叉树时,以root为根的树就是搜索二叉树
}
中序遍历非递归
public boolean isBST3(TreeNode root){
if (root == null){
return true;
}
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || root != null){
if (root != null){
stack.push(root);
root = root.left;
}else{
root = stack.pop();
// System.out.println(root.val + " ");
if (pre >= root.val){
return false;
}else {
pre = root.val;
}
root = root.right;
}
}
return true;
}
通用套路
当我们要判断root是否是搜索二叉树的时候,我们需要:
- 左子树是搜索二叉树
- 右子树是搜索二叉树
- root大于左子树的最大值
- root小于右子树的最小值
所以我们需要的左子树信息是:
- 左子树是否是搜索二叉树
- 左子树的最大值
需要的右子树的信息:
- 右子树是否是搜索二叉树
- 右子树的最小值
结果左右子树的规模就不一样了,那么我们就规定递归需要三个信息,将递归的规模统一:
- 子树是否是搜索二叉树
- 子树的最大值
- 子树的最小值
有了左右子树的三个信息后,就可以知道root是否是搜索二叉树,以及搜索二叉树的最大值和最小值。
public boolean isBST4(TreeNode root){
return process(root).isBST;
}
public class ReturnType{
boolean isBST;
int max;
int min;
public ReturnType(boolean isBST, int max, int min) {
this.isBST = isBST;
this.max = max;
this.min = min;
}
}
public ReturnType process(TreeNode root){
if (root == null){
return null;
}
ReturnType leftData = process(root.left);
ReturnType rightData = process(root.right);
int min = root.val;
int max = root.val;
//求了左子树的最大值和最小值后,又去与右子树比较,可以得到root的最大值和最小值
//左子树的最大值和最小值
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);
}//min是root树下的最小值;max是root树下的最大值。然后最后一行代码返回的时候,就得到了当前树也就是root的最大值和最小值
boolean isBST = true;
//左子树不平衡;或左子树的根小于等于左子树中的最小值,那么root这棵树就不平衡
if (leftData != null && (!leftData.isBST || leftData.max >= root.val)){
isBST = false;
}
//右子树同理
if (rightData != null && (!rightData.isBST || rightData.min <= root.val)){
isBST = false;
}
//返回我们需要的三个信息
return new ReturnType(isBST,max,min);
}
4. 判断完全二叉树
- 采用层序遍历
- 如果一棵子树的根结点,它的左子树为空但右子树不为空,则这颗子树一定不是完全二叉树
- 满足上面条件的情况下,如果遇到了一棵子树,左孩子不为空但右孩子为空,则之后层序遍历的所有结点必须是叶子结点
public boolean isCBT(TreeNode root){
if (root == null){
return true;
}
LinkedList<TreeNode> queue = new LinkedList<>();
boolean leaf = false;
queue.add(root);
while (!queue.isEmpty()){
TreeNode cur = queue.poll();
if ((leaf && (cur.left!=null || cur.right != null)) || (cur.left == null && cur.right != null)){
return false;
}
if (cur.left != null && cur.right == null){
leaf = true;
}
if (cur.left != null){
queue.add(cur.left);
}
if (cur.right != null){
queue.add(cur.right);
}
}
return true;
}
5. 判断平衡二叉树
后序遍历
对二叉树做后序遍历,从底至顶返回子树深度,若遇到子树不是平衡二叉树,则进行剪枝,直接向上返回。
- 返回值
- 当左右子树深度差小于等于1,就返回当前子树的深度
- 当左右子树深度差大于1,返回-1,表示子树不是平衡二叉树
- 终止条件
- 当root为空,返回高度为0
- 当左子树或者右子树深度为-1,说明左子树或者右子树不是平衡二叉树,剪枝,返回-1
public boolean isBalanced(TreeNode root){
return recur(root) != -1;
}
public int recur(TreeNode root){
if (root == null){
return 0;
}
int left = recur(root.left);
//剪枝
if (left == -1){
return -1;
}
int right = recur(root.right);
//剪枝
if (right == -1){
return -1;
}
return Math.abs(left - right) <= 1 ? Math.max(left, right) + 1 : -1;
}
通用套路
一棵树是平衡二叉树必须满足三个条件:左子树是平衡二叉树;右子树是平衡二叉树;左右子树高度差小于等于1。
要判断root是否是平衡二叉树,我们就需要根据左右子树的信息来进行判断。
需要的左子树的信息:
- 左子树是否是平衡二叉树
- 左子树的高度
需要的右子树的信息:
- 右子树是否是平衡二叉树
- 右子树的高度
有了左右子树的信息,就可以知道左右子树是否是平衡二叉树,以及左右子树的高度差,然后就可以判断root是否是平衡二叉树。
在进行递归的时候,我们就需要:子树的高度、子树是否是平衡二叉树。返回的时候就可以得到root的两个信息。
public boolean isBalanced2(TreeNode root){
return process(root).isBalanced;
}
public class ReturnType{
public boolean isBalanced;//是否是平衡二叉树
public int height;//高度
public ReturnType(boolean isBalanced, int height) {
this.isBalanced = isBalanced;
this.height = height;
}
}
public ReturnType process(TreeNode root){
if (root == null){
return new ReturnType(true, 0);//高度为0的平衡二叉树
}
ReturnType leftData = process(root.left);
ReturnType rightData = process(root.right);
int height = Math.max(leftData.height, rightData.height) + 1;
//左子树是平衡二叉树、右子树是平衡二叉树、左右子树高度差小于等于1
boolean isBalanced = leftData.isBalanced && rightData.isBalanced && Math.abs(leftData.height - rightData.height) <= 1;
return new ReturnType(isBalanced, height);
}
6. 判断满二叉树
public boolean isFullTree(TreeNode root){
if (root == null){
return true;
}
ReturnType ret = isFull(root);
return ret.node_cnt == (1 << ret.node_cnt - 1);
}
public class ReturnType{
int height;
int node_cnt;
public ReturnType(int height, int node_cnt) {
this.height = height;
this.node_cnt = node_cnt;
}
}
public ReturnType isFull(TreeNode root){
if (root == null){
return new ReturnType(0, 0);
}
ReturnType leftData = isFull(root.left);
ReturnType rightData = isFull(root.right);
int height = Math.max(leftData.height, rightData.height) + 1;
int node_cnt = leftData.node_cnt + rightData.node_cnt + 1;
return new ReturnType(height, node_cnt);
}
7. 给定两个二叉树的结点p和q,找到它们的最低公共祖先结点
递归
终止条件
-
root为空时返回null
-
root等于p或者q时,返回root
如果p是q的根或者q是p的根,那么直接返回p或者q
如果pq在两侧,加入先递归到p,则返回p,后面遇到q的时候返回q,就会满足返回值中的第二种情况。
递推
- 递归左子树,返回left
- 递归右子树,返回right
返回值
-
当left和right都为空,返回null
说明root的左右子树中都不包含p和q
-
当left和right都不为空,返回root
说明p、q分列在root的异侧,即分别在root的左右子树,则root为p和q的最近公共祖先
-
当left为空,right不为空,返回right
说明pq都不在root的左子树中
-
当left不为空,right为空,返回left
TreeNode lowestAncestor(TreeNode root, TreeNode p, TreeNode q){
if (root == null || root == p || root == q){
return root;
}
TreeNode left = lowestAncestor(root.left, p, q);
TreeNode right = lowestAncestor(root.right, p, q);
// if (left == null && right == null){
// return null;
// }
// if (left != null && right != null){
// return root;
// }
// if (left == null && right != null){
// return right;
// }
// if (left != null && right == null){
// return left;
// }
if (left == null){
return right;
}
if (right == null){
return left;
}
return root;
}
哈希表
哈希表fatherMap存储:key是当前结点,value是key的父结点
由于fatherMap表,我们就可以从p结点开始,依次获得它的父结点,然后一直往上直到根结点,将这个路线中的所有父结点存储在set中。
再从q开始,往上走,如果过程中遇到的结点出现在了set中,说明这个结点就是pq的公共祖先。
public TreeNode lowestAncestor2(TreeNode root, TreeNode p, TreeNode q){
HashMap<TreeNode, TreeNode> fatherMap = new HashMap<>();
//先将根结点放入map
fatherMap.put(root, root);
//递归,将所有结点和其父结点的键值对放入表中
recur(root, fatherMap);
HashSet<TreeNode> set = new HashSet<>();
TreeNode cur = p;
while (fatherMap.get(cur) != cur){//根结点的父结点等于本身
set.add(cur);
cur = fatherMap.get(cur);//遍历父结点
}
//根结点也要加入set中
set.add(cur);
cur = q;
while (!set.contains(cur)){
cur = fatherMap.get(cur);
}
return cur;
}
public void recur(TreeNode root, HashMap<TreeNode, TreeNode> fatherMap){
if (root == null){
return;
}
fatherMap.put(root.left, root);
fatherMap.put(root.right, root);
recur(root.left, fatherMap);
recur(root.right, fatherMap);
}
8. 在二叉树中找到一个结点的后继结点
-
x的前驱结点是x的左子树的最右结点,后继节点是右子树的最左结点
-
当x有右子树的时候,右子树的最左结点就是x的后继结点
-
当x无右子树的时候,假设x的后继结点是y(y的前驱节点是x),则y的左子树的最右结点是x。如何找到y?
-
所以我们需要从x出发,向上寻找,依次遍历x的父结点。直到x是某个结点的左孩子,则这个结点就是我们要找的y,这个结点的左子树的最右结点就是x。
-
如果parent为空的时候都没有找到,就说明x是整棵树的最右结点,即中序遍历下的最后一个结点,它没有后继结点。
-
public class Node {
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int data) {
this.value = data;
}
}
public Node getSuccessorNode(Node node){
if (node == null){
return null;
}
if (node.right != null){
return getLeftMost(node.right);
}
Node parent = node.parent;
//node != parent.left :从x出发,向上寻找一个父结点,这个父结点是y的左子树的时候,就找到了y是x的后继结点
while (parent != null && node != parent.left){
node = parent;
parent = node.parent;
}
return parent;
}
public Node getLeftMost(Node node){
if (node == null){
return null;
}
while (node.left != null){
node = node.left;
}
return node;
}
9. 二叉树的序列化和反序列化
先序遍历
/**
* 先序序列化
* @param root
* @return
*/
public String serialByPre(TreeNode root){
if (root == null){
return "#";
}
String res = root.val + ",";
res += serialByPre(root.left);
res += serialByPre(root.right);
return res;
}
/**
* 先序反序列化
* @param preStr
* @return
*/
public TreeNode reconByPreString(String preStr){
String[] values = preStr.split(",");
return reconPreOrder(values);
}
int index = 0;
public TreeNode reconPreOrder(String[] values){
String value = values[index++];
if (value.equals("#")){
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(value));
root.left = reconPreOrder(values);
root.right = reconPreOrder(values);
return root;
}
层序遍历
public String serialByLevel(TreeNode root){
if (root == null){
return "#,";
}
String res = root.val + ",";
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode cur = queue.poll();
TreeNode left = cur.left;
TreeNode right = cur.right;
if (left != null){
queue.add(left);
res += left.val + ",";
}else {
res += "#,";
}
if (right != null){
queue.add(right);
res += right.val + ",";
}else {
res += "#,";
}
}
return res;
}
public TreeNode reconByLevelString(String levelStr){
String[] values = levelStr.split(",");
int index = 0;
LinkedList<TreeNode> queue = new LinkedList<>();
if (values[index].equals("#")){
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(values[index++]));
queue.add(root);
while (!queue.isEmpty()){
TreeNode cur = queue.poll();
String leftValue = values[index++];
if (leftValue.equals("#")){
cur.left = null;
}else {
cur.left = new TreeNode(Integer.valueOf(leftValue));
queue.add(cur.left);
}
String rightValue = values[index++];
if (rightValue.equals("#")){
cur.right = null;
}else {
cur.right = new TreeNode(Integer.valueOf(rightValue));
queue.add(cur.right);
}
}
return root;
}
10. 折纸问题
- 折纸的次数对应二叉树的层数
- 凹折痕位于左子树,凸折痕位于右子树
- 二叉树中序遍历的结果就是折痕的顺序
//i结点的层数
//down==true表示凹结点;down==false表示凸结点
public void printProcess(int i, int N, boolean down){
//层数越界
if (i > N){
return;
}
//模拟二叉树
printProcess(i + 1, N, true);//模拟遍历左子树,表示递归到第i+1层,是第i层的左子树
System.out.println(down ? "凹" : "凸");
printProcess(i + 1, N, false);
}
printProcess(1, N, true);
标签:结点,return,cur,二叉树,005,null,root,left
From: https://www.cnblogs.com/jyyyy/p/18012182