一、前言
最近正在准备找实习,因此拿起《剑指Offer》来看看,突击下自己的基础。但是《剑指Offer》上面的算法都是使用C语言,个人对C语言不熟悉,因此使用自己熟悉的Java来实现。嗯,以后要是机试啥的,能用Java我就用Java,C太恶心了⊙﹏⊙!
二、问题描述
<span style="font-family:Microsoft YaHei;font-size:18px;">/**
* 题目:重建二叉树
* 描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
* 用例:例如输入前序遍历序列 {1,2,4,7,3,5,6,8} 和中序遍历序列{4,7,2,1,5,3,8,6}, 则重建出二叉树,并输出它的头结点。
* 结果:头结点为 1
* 1
* / \
* 2 3
* / / \
* 4 5 6
* \ /
* 7 8
* @author johnnie
*
*/</span>
在《剑指Offer》上学到一点,测试用例一定要全!!!边界值和异常输入都要好好考虑!!!
三、解题思路
我们知道前序遍历序列中,第一个值就是树根节点的值,在中序遍历序列中,根节点左侧的是左子树节点,右侧的是右子树节点。因此根据这个规律,我们就可以使用递归,来分别构建左右子树。
四、代码实现
4.1 核心算法:buildBinaryTree() 函数
<span style="font-family:Microsoft YaHei;font-size:18px;"> /**
* 构建二叉树
* @param preOrder 前序遍历序列
* @param inOrder 中序遍历序列
* @param length 序列长,重构左子树,就是左子树序列长;重构右子树,就是右子树序列长
* @return 重建后的树根节点
* @throws Exception
*/
public BinaryTreeNode buildBinaryTree (String preOrder, String inOrder, int length) throws Exception {
// 判断输入是否为空
if (!ValidateUtils.isValid(preOrder) || !ValidateUtils.isValid(inOrder)) {
return null;
}
// 判断输入的前序中序序列长度是否相等,不等,则是非法输入
if (preOrder.length() != inOrder.length()) {
throw new Exception("输入非法");
}
BinaryTreeNode root = new BinaryTreeNode();
// 前序序列中第一个值是根节点的值
root.setValue(preOrder.charAt(0));
root.setLeft(null);
root.setRight(null);
// 基准情形--->什么时候递归结束
if (length == 1) {
return root;
}
// 在中序遍历序列中查找根节点所在位置
int rootIndex = 0;
while (rootIndex <= inOrder.length() && root.getValue() != inOrder.charAt(rootIndex)) {
rootIndex ++;
}
// 计算左右子树节点数
int leftLength = rootIndex;
int rightLength = preOrder.length() - rootIndex - 1; // 减去1是根节点所占
if (leftLength > 0) {
// 重构左子树
/*
* 校验:前序中序的序列是否合法
*/
String leftPreOrder = preOrder.substring(1, leftLength + 1);
String leftInOrder = inOrder.substring(0, rootIndex);
// 先判断长度
if (leftPreOrder.length() != leftInOrder.length()) {
throw new Exception("非法输入");
}
// 将其转为集合,进行判断两个集合元素是否一致
List<String> preList = StringUtils.toList(leftPreOrder);
List<String> inList = StringUtils.toList(leftInOrder);
if (!preList.containsAll(inList)) {
throw new Exception("非法输入");
}
root.setLeft(buildBinaryTree(leftPreOrder, leftInOrder, leftLength));
}
if (rightLength > 0) {
// 重构右子树: 此处截取的 inOrder 应该是 rootIndex + 1,在根节点的索引上加1开始截取右子串
/*
* 校验:前序中序的序列是否合法
*/
String rightPreOrder = preOrder.substring(leftLength + 1);
String rightInOrder = inOrder.substring(rootIndex + 1);
// 1. 先判断长度
if (rightPreOrder.length() != rightInOrder.length()) {
throw new Exception("非法输入");
}
// 2. 将其转为集合,进行判断两个集合元素是否一致
List<String> preList = StringUtils.toList(rightPreOrder);
List<String> inList = StringUtils.toList(rightInOrder);
if (!preList.containsAll(inList)) {
throw new Exception("非法输入");
}
root.setRight(buildBinaryTree(rightPreOrder, rightInOrder, rightLength));
}
return root;
}</span>
注:
- 代码中加入了一些校验合法性的判断
- 也加入了一些自定义工具类,比如:ValidateUtils、StringUtils、PrintUtils,稍候会在文章末尾贴出其代码
4.2 二叉树节点定义
/**
* 二叉树节点定义
* @author johnnie
*
*/
public class BinaryTreeNode {
private char value; // 值
private BinaryTreeNode left; // 左孩子
private BinaryTreeNode right; // 右孩子
public char getValue() {
return value;
}
public void setValue(char value) {
this.value = value;
}
public BinaryTreeNode getLeft() {
return left;
}
public void setLeft(BinaryTreeNode left) {
this.left = left;
}
public BinaryTreeNode getRight() {
return right;
}
public void setRight(BinaryTreeNode right) {
this.right = right;
}
}
4.3 测试用例
按照《剑指Offer》的指导,给出了7个测试用例,分别针对普通二叉树、特殊二叉树、非法输入进行测试
<span style="font-family:Microsoft YaHei;font-size:18px;"> /**
* 7. 特殊输入:前序和中序序列不匹配--->通过,抛出异常
* @param rbt
* @throws Exception
*/
private static void test7(RebuildBinaryTree rbt) throws Exception {
BinaryTreeNode root;
String preOrder = "1245367";
String inOrder = "4281637";
root = rbt.buildBinaryTree(preOrder, inOrder, preOrder.length());
print(root);
}
/**
* 6. 特殊输入:根节点为 null--->通过,无输出
* @param rbt
* @throws Exception
*/
private static void test6(RebuildBinaryTree rbt) throws Exception {
BinaryTreeNode root;
String preOrder = "";
String inOrder = "";
root = rbt.buildBinaryTree(preOrder, inOrder, preOrder.length());
print(root);
}
/**
* 5. 特殊二叉树:树中只有一个结点--->通过
* @param rbt
* @throws Exception
*/
private static void test5(RebuildBinaryTree rbt) throws Exception {
BinaryTreeNode root;
String preOrder = "1";
String inOrder = "1";
root = rbt.buildBinaryTree(preOrder, inOrder, preOrder.length());
print(root);
}
/**
* 4. 特殊二叉树:所有结点都没有左子结点--->通过
* @param rbt
* @throws Exception
*/
private static void test4(RebuildBinaryTree rbt) throws Exception {
BinaryTreeNode root;
String preOrder = "12345";
String inOrder = "12345";
root = rbt.buildBinaryTree(preOrder, inOrder, preOrder.length());
print(root);
}
/**
* 3. 特殊二叉树:所有结点都没有右子结点--->通过
* @param rbt
* @throws Exception
*/
private static void test3(RebuildBinaryTree rbt) throws Exception {
BinaryTreeNode root;
String preOrder = "12345";
String inOrder = "54321";
root = rbt.buildBinaryTree(preOrder, inOrder, preOrder.length());
print(root);
}
/**
* 2. 普通二叉树:完全二叉树--->通过
* @param rbt
* @throws Exception
*/
private static void test2(RebuildBinaryTree rbt) throws Exception {
BinaryTreeNode root;
String preOrder = "1245367";
String inOrder = "4251637";
root = rbt.buildBinaryTree(preOrder, inOrder, preOrder.length());
print(root);
}
/**
* 1. 普通二叉树:不完全二叉树--->通过
* @param rbt
* @throws Exception
*/
private static void test1(RebuildBinaryTree rbt) throws Exception {
BinaryTreeNode root;
String preOrder = "12473568"; // 前序遍历序列
String inOrder = "47215386"; // 中序遍历序列
root = rbt.buildBinaryTree(preOrder, inOrder, preOrder.length());
print(root);
}</span>
/**
* 打印树,用于验证
* @param root
*/
private static void print(BinaryTreeNode root) {
PrintUtils.print("--------------------------------");
PrintUtils.print("前序:");
PrintUtils.printBinaryTree(root, PrintUtils.ORDER_PRE);
PrintUtils.print("中序:");
PrintUtils.printBinaryTree(root, PrintUtils.ORDER_IN);
PrintUtils.print("后序:");
PrintUtils.printBinaryTree(root, PrintUtils.ORDER_POST);
PrintUtils.print("--------------------------------");
}
测试结果,全部通过!
五、工具类代码
5.1 PrintUtils
<span style="font-family:Microsoft YaHei;font-size:18px;">/**
* 输出工具类
* @author johnnie
*
*/
public class PrintUtils {
public static final String ORDER_PRE = "pre";
public static final String ORDER_IN = "in";
public static final String ORDER_POST = "post";
/**
* 打印一串文本
* @param str
*/
public static void print (String str) {
System.out.println(str);
}
/**
* 打印 int 数组,以空格分隔
*/
public static void printIntArr (Integer[] arr) {
for (int i = 0; i < arr.length; i ++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
/**
* 打印数
* @param root 树根节点
* @param type 打印的方式,pre | in | after
*/
public static void printBinaryTree (BinaryTreeNode root, String type) {
switch (type) {
case ORDER_PRE:
printBinaryTreeWithPre(root);
System.out.println();
break;
case ORDER_IN:
printBinaryTreeWithIn(root);
System.out.println();
break;
case ORDER_POST:
printBinaryTreeWithPost(root);
System.out.println();
break;
}
}
/**
* 前序遍历二叉树,输出前序遍历序列,以空格分隔
* @param root
*/
private static void printBinaryTreeWithPre (BinaryTreeNode root) {
// 合法性校验
if (root != null) {
// 输出根节点
System.out.print(root.getValue() + " ");
// 递归遍历左子树
if (root.getLeft() != null) {
printBinaryTreeWithPre(root.getLeft());
}
// 递归遍历右子树
if (root.getRight() != null) {
printBinaryTreeWithPre(root.getRight());
}
}
}
/**
* 中序遍历二叉树,输出中序遍历序列,以空格分隔
* @param root
*/
private static void printBinaryTreeWithIn (BinaryTreeNode root) {
// 合法性校验
if (root != null) {
// 递归遍历左子树
if (root.getLeft() != null) {
printBinaryTreeWithIn(root.getLeft());
}
// 输出根节点
System.out.print(root.getValue() + " ");
// 递归遍历右子树
if (root.getRight() != null) {
printBinaryTreeWithIn(root.getRight());
}
}
}
/**
* 后序遍历二叉树,输出后序遍历序列,以空格分隔
* @param root
*/
private static void printBinaryTreeWithPost (BinaryTreeNode root) {
// 合法性校验
if (root != null) {
// 递归遍历左子树
if (root.getLeft() != null) {
printBinaryTreeWithPost(root.getLeft());
}
// 递归遍历右子树
if (root.getRight() != null) {
printBinaryTreeWithPost(root.getRight());
}
// 输出根节点
System.out.print(root.getValue() + " ");
}
}
}</span>
5.2 StringUtils
<span style="font-family:Microsoft YaHei;font-size:18px;">/**
* 字符串工具类
* @author johnnie
*
*/
public class StringUtils {
/**
* 字符串转集合
* @return
*/
public static List<String> toList (String str) {
String[] arr = str.split("");
List<String> list = Arrays.asList(arr); // 返回的是一个代理,不能修改内部结构
List<String> rs = new ArrayList<String>(list); // 包装下,可修改
return rs;
}
}</span>
5.3 ValidateUtils
<span style="font-family:Microsoft YaHei;font-size:18px;">/**
* 检验工具类
* @author johnnie
*
*/
public class ValidateUtils {
private ValidateUtils () {}
/**
* 判断数组是否可用
* @param objs 对象数组
* @return true:可用,false:不可用
*/
public static boolean isValid (Object[] objs) {
if (objs == null || objs.length == 0) {
return false;
}
return true;
}
/**
* 校验字符串是否合法
* @param str
* @return
*/
public static boolean isValid (String str) {
if (str == null || "".equals(str)) {
return false;
}
return true;
}
}</span>