Java笔记---剑指Offer(一:Java实现重建二叉树)

版权声明:博客编写不易,转载时请注明出处,Thank you very much! https://blog.csdn.net/GuLu_GuLu_jp/article/details/50833413

一、前言

最近正在准备找实习,因此拿起《剑指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>

Note:前往 bascker/javaworld 获取更多 Java 知识


猜你喜欢

转载自blog.csdn.net/GuLu_GuLu_jp/article/details/50833413