二叉树是一种在实际编程中经常遇到的数据结构。二叉树的遍历是二叉树相关知识的基础,它经常是解决复杂问题的基石。二叉树的遍历有前序遍历、中序遍历、后序遍历和宽度遍历。
另外,二叉树相关的问题,常常会使用递归的方法解决。因此,可以在练习二叉树相关问题的同时提高递归方法的编写能力。
问题简介
1、*二叉树的非递归打印
2、平衡二叉树的判断
3、完全二叉树的判断
4、折纸问题
5、寻找错误节点
6、*树上最远距离的问题
7、**最大二叉搜索子树问题
问题详述
1、二叉树的非递归打印
描述:请用非递归方式实现二叉树的先序、中序和后序的遍历打印。 给定一个二叉树的根结点root,请依次返回二叉树的先序,中序和后续遍历。
分析:分别介绍树的非递归方法遍历。
先序遍历,只需要设置一个辅助堆栈stack即可,具体操作流程如下:首先,将根节点放入stack,然后弹出stack栈顶元素记录其值,并将其右孩子和左孩子先后压入栈中,再从栈中找到栈顶元素访问并将栈顶元素的右孩子和左孩子压入栈中,重复以上压入和弹出操作直到栈空为止就可以得到先序遍历结果了。
中序遍历相对先序遍历的实现比较复杂,它借助指向访问节点的指针ptr和一个辅助栈stack,具体步骤如下:首先,将根节点放入堆栈;然后,如果ptr不为空,则将ptr压入stack,并将ptr指向ptr的左孩子,如果ptr为空,那么就将栈顶元素弹出,访问该节点,并将ptr指向弹出元素的右孩子;最后,重复以上操作,直到stack为空。
后序遍历是三种遍历方式中最复杂的。它需要两个辅助栈stack1和stack2。后序遍历步骤如下:首先,将根节点放入stack1;然后,将栈顶元素top弹出并放入stack2中,另外,top的左孩子不为空将其放入stack1,top的右孩子不为空将其放入stack1;重复操作,直到stack1为空,顺序弹出stack2,就能得到树的后续遍历序列。
示例代码:
public void pre2(TreeNode root, List<Integer> items) {// 容易理解
if (root == null) { return; }
Stack<TreeNode> help = new Stack<TreeNode>();
help.push(root);
while (!help.isEmpty()) {
TreeNode item = help.pop();
items.add(item.val);
if (item.right != null) {
help.push(item.right);
}
if (item.left != null) {
help.push(item.left);
}
}
}
public void mid2(TreeNode root, List<Integer> items) {// 比较困难
if (root == null) { return; }
Stack<TreeNode> help = new Stack<TreeNode>();
while (!help.isEmpty() || root != null) {
if (root != null) {
help.push(root);
root = root.left;
} else {
TreeNode item = help.pop();
items.add(item.val);
root = item.right;
}
}
}
public void pos2(TreeNode root, List<Integer> items) {
if (root == null) { return; }
Stack<TreeNode> stack1 = new Stack<TreeNode>();
Stack<TreeNode> stack2 = new Stack<TreeNode>();
stack1.push(root);
while (!stack1.isEmpty()) {
TreeNode item = stack1.pop();
stack2.push(item);
if (item.left != null) { stack1.push(item.left); }
if (item.right != null) { stack1.push(item.right); }
}
while (!stack2.isEmpty()) { items.add(stack2.pop().val); }
}
总结:待整理
2、平衡二叉树的判断
描述:给定二叉树的根结点root,请返回一个bool值,代表这棵树是否为平衡二叉树。
分析:借助树的递归后续遍历,如果某一节点的左子树和右子树的返回值left 和right有一个小0,那么就返回-1;当left-right大于1或小于-1时,同样返回-1;都不符合时,返回left > right ? left + 1 : right + 1;递归结束后,返回值为-1,说明是非平衡树,否则为平衡二叉树。
示例代码:
public int chk(TreeNode root) {
if (root == null) { return 0; }
int left = chk(root.left);
int right = chk(root.right);
if (left < 0 || right < 0) { return -1; }
if (left - right > 1 || left - right < -1) { return -1; }
return left > right ? left + 1 : right + 1;
}
总结:待整理
3、完全二叉树的判断
描述:给定一棵二叉树,判断其是不是完全二叉树。
分析:完全二叉树除最后一层外每层都是满的,并且最后一层的节点都集中在左侧。结合这个性质,使用层序遍历的方式来解决这个问题。非完全树的情况: 1)某个节点左子树为空,右子树不为空;2)最后一层的节点中间有间断。详细内容看代码。
示例代码:
public boolean chk(TreeNode root) {
if (root == null) return true;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
boolean flag = true;
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode cur = queue.poll();
if ((cur.left == null && cur.right != null) ||
(!flag && (cur.left != null || cur.right != null))) {
return false;
}
if (cur.left != null) { queue.offer(cur.left); }
else { flag = false; }
if (cur.right != null) {
queue.offer(cur.right);
} else {
flag = false;
}
}
return true;
}
总结:待整理
4、折纸问题
描述:请把纸条竖着放在桌上,然后从纸条的下边向上对折,压出折痕后再展开。此时有1条折痕,突起指向纸条的背,这条折痕叫做“下”折痕 ;突起指向纸条正的折痕叫做“上”折痕。如果每次都从下边向上对折,对折N次。请从上到下计算出所有折痕的方向。给定折的次数n,请返回从上到下的折痕的数组,若为下折痕则对应元素为”down”,若为上折痕则为”up”.
测试样例:1 返回:[“down”]
分析:可以将问题转化为一棵二叉树。每次醉着都会在上次没个折痕的上下出现“下”折痕和“上”折痕。
示例代码:
public String[] foldPaper(int n) {
List<String> list = new ArrayList<String>();
fold(1, n, true, list);
String[] strings = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
strings[i] = list.get(i);
}
return strings;
}
private void fold(int i, int n, boolean down, List<String> list) {
if (i > n)
return;
fold(i + 1, n, true, list);
list.add(down ? "down" : "up");
fold(i + 1, n, false, list);
}
总结:待整理
5、寻找错误节点
描述:一棵二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这棵二叉树不再是搜索二叉树,请找到这两个错误节点并返回他们的值。保证二叉树中结点的值各不相同。 给定一棵树的根结点,请返回两个调换了位置的值,其中小的值在前。
分析:二叉搜索树的中序遍历是一个有序的数组。中序遍历题目中的这个二叉树,找到第一个逆序的最大值,和第二个逆序的最小值,就可以得到这两个逆序的值。
示例代码:
public int[] findError(TreeNode root) {
List<Integer> list = new ArrayList<Integer>();
getMidSequence(root, list);
int[] ret = new int[2];
boolean flag = false;
for (int i = 0; i < list.size() - 1; i++) {
if (list.get(i) > list.get(i + 1)) {
ret[1] = list.get(i + 1);
if (!flag) {
flag = true;
ret[0] = list.get(i);
}
}
}
ret[0] = ret[0] ^ ret[1];//交换ret[0]和ret[1]的值
ret[1] = ret[0] ^ ret[1];
ret[0] = ret[0] ^ ret[1];
return ret;
}
public void getMidSequence(TreeNode root, List<Integer> list) {
if (root == null) {
return;
}
getMidSequence(root.left, list);
list.add(root.val);
getMidSequence(root.right, list);
}
总结:待整理
6、树上最远距离的问题
描述:从二叉树的节点A出发,可以向上或者向下走,但沿途的节点只能经过一次,当到达节点B时,路径上的节点数叫作A到B的距离。对于给定的一棵二叉树,求整棵树上节点间的最大距离。 给定一个二叉树的头结点root,请返回最大距离。保证点数大于等于2小于等于500.
分析:出现路径距离最远有三种情况:左子树最远距离、右子树的最远距离和左右子树根节点为终点的路径和+1。
示例代码:
public int findLongest(TreeNode root) {
int[] record = new int[1];
return process(root, record);
}
public int process(TreeNode root, int[] record) {
if (root == null) {
record[0] = 0;
return 0;
}
int lmax = process(root.left, record);//左子树的最长距离
int maxFromLeft = record[0];
int rmax = process(root.right, record);//有子树的最长距离
int maxFromRight = record[0];
int cur = maxFromLeft + maxFromRight + 1;//左右两个最长路径的和
record[0] = Math.max(maxFromRight, maxFromLeft) + 1;
//该参数用上层递归,可理解为以该节点为终点的最长路径
return Math.max(Math.max(lmax, rmax), cur);
}
总结:待整理
7、最大二叉搜索子树问题
描述:有一棵二叉树,其中所有节点的值都不一样,找到含有节点最多的搜索二叉子树,并返回这棵子树的头节点。给定二叉树的头结点root,请返回所求的头结点,若出现多个节点最多的子树,返回头结点权值最大的。
分析:使用递归的前序遍历函数f,f中使用一个递归判断搜索二叉树的函数。
示例代码:
public class MaxTree {
public TreeNode head;
public int size = 0;
public TreeNode getMax(TreeNode root) {
findmaxhead(root);
return head;
}
public void findmaxhead(TreeNode root) {
//递归遍历树的每个节点(记为Ni),判断Ni是否为二叉搜索树
if (root == null)
return;
int[] siz = new int[1];
if (isSST(root, Integer.MIN_VALUE, Integer.MAX_VALUE, siz)) {
if (siz[0] > size) {
size = siz[0];
head = root;
} else if (siz[0] == size && root.val > head.val) {
head = root;
}
}
findmaxhead(root.left);
findmaxhead(root.right);
}
public boolean isSST(TreeNode root, int min, int max, int[] siz) {
//判断root节点是否是二叉搜索树的根节点
if (root == null)
return true;
if (isSST(root.left, min, root.val, siz) && isSST(root.right, root.val, max, siz)) {
if (root.val > min && root.val < max) {
siz[0] += 1;
return true;
}
}
return false;
}
}
总结:编程会使用类的属性作为保存返回值。问题中使用了递归嵌套。