(剑指offer笔记)根据前序遍历和中序遍历重建二叉树

问题:输入某二叉树的前序遍历和中序遍历的结果,重建出该二叉树,假设输入的前序遍历和终须遍历的结果都不含有重复数字。最后按层遍历输出二叉树结果。

	public static void main(String[] args) {
		// TODO 自动生成的方法存根
		Character[] preOrder = new Character[] { 'a','b','d','g','c','e','f','h' };
		Character[] inOrder = { 'd','g','b','a','e','c','h','f'};
		Entry<Character> root = construct(preOrder, inOrder);
		System.out.println(toString(root));
	}

规定二叉树结点不含父节点字段。

	private static class Entry<E> {
		Entry<E> left, right;
		E element;

		Entry() {
		}

		Entry(E element) {
			this.element = element;
		}
	}


解答:

前序遍历(根-左-右)

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. preOrder(t){  
  2.     if(t非空){  
  3.         访问t的根元素;  
  4.         preOrder(t的左子树);      
  5.         preOrder(t的右子树);      
  6.     }  
  7. }  

中序遍历(左-根-右)
[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. inOrder(t){  
  2.     if(t非空){  
  3.         inOrder(t的左子树);  
  4.         访问t的根元素;  
  5.         inOrder(t的右子树);  
  6.     }  
  7. }  
前序遍历结果数组最左边总是根节点,而中序遍历结果数组中间某位置是根节点,左子树在根节点左边,右子树在根节点右边。我们现在前序遍历结果数组中得到根节点的值,然后在中序遍历结果数组中查找它,找到后对左右子树用同样方法迭代发现其根节点、左子树、右子树,由此构建二叉树。


程序使用泛型和迭代:

构建二叉树的函数分为两层,一层为public方法,可供外界程序调用;一层为private方法,实现构建功能:

	public static <E> Entry<E> construct(E[] preOrder, E[] inOrder) {
		if (preOrder == null || inOrder == null
				|| preOrder.length != inOrder.length || inOrder.length <= 0)
			return null;//检查输入的数组是否有效
		return coreConstruct(preOrder, 0, preOrder.length - 1, inOrder, 0,
				preOrder.length - 1);
	}


    private static <E> Entry<E> coreConstruct(E[] preOrder, int startPreOrder,
			int endPreOrder, E[] inOrder, int startInOrder, int endInOrder) {
	    //越界检查,递归终止
		if(startPreOrder>endPreOrder||startInOrder>endInOrder)
		    return null;
		Entry<E> root = new Entry<E>(preOrder[startPreOrder]);// 前序遍历数组的最左边的元素是二叉树的根节点
		// 在中序遍历数组中找到该根节点元素,它左边是左子树,右边是右子树
		int rootInOrder = search(inOrder, root.element);//找到根结点索引
		if (rootInOrder == -1) {//如果没找到该结点元素,说明给定的数组有问题
			throw new IllegalArgumentException("输入数组无效");
		}
		int leftLength = rootInOrder - startInOrder;//左子树长度
		int leftEndPreOrder = startPreOrder + leftLength;//前序遍历结果数组里当前根结点的左子树的最后一个元素的位置。
		// 对其左子树和右子树迭代重建
		if (leftLength > 0) {
			// 如果左子树长度大于0,那就对左子树进行重建
			root.left = coreConstruct(preOrder, startPreOrder + 1,
					leftEndPreOrder, inOrder, startInOrder, rootInOrder - 1);
		}
		if (leftEndPreOrder < endPreOrder) {
			//如果左子树最后一个元素的索引位置小于整个前序遍历数组最后一个索引位置,说明有右子树存在, 重建右子树
			root.right = coreConstruct(preOrder, leftEndPreOrder + 1,
					endPreOrder, inOrder, rootInOrder + 1, endInOrder);
		}
		return root;
	}


	/**
	 * @param array 待搜索的数组
	 * @param e 要被搜索的元素
	 * @return 被搜索的元素在数组中的位置。如果没有找到,就返回-1
	 */
	public static <E> int search(E[] array, E e) {
		for (int i = 0; i < array.length; i++) {
			if (array[i].equals(e))
				return i;
		}
		return -1;
	}


重建完毕后需要按层遍历输出

逐层遍历(宽度优先遍历)

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. breadthFirst(t){  
  2. //queue为二叉树(引用)的队列  
  3. //t为二叉树(引用)  
  4.     if(t非空){  
  5.         queue.enqueue(t); 
  6. while(queue不空){
  7. queue.dequeue(t); //从队列中移除根结点
  8. 访问移除的根;  
            if(tree的左子树非空)  
                queue.enqueue(tree的左子树);  
            if(tree的右子树非空)  
                queue.enqueue(tree的右子树); 
  9. }          
  10.     }  
  11. }  
核心思想是利用队列的先进先出特点,根结点先进去,叶子节点后进去。用一个队列保存一层的结点,当访问该层某结点的子节点时,就被该结点移除出队列,访问它,然后在队列末尾插入子节点。在while循环中不断重复该过程。于是所有节点就按照从根开始一层一层地从左到右地访问完毕。

	/**
	 * 宽度遍历,用一个链表保存结果
	 * 
	 * @param root
	 * @return
	 */
	public static <E> List<E> breadthFirst(Entry<E> root) {
		LinkedList<Entry<E>> list = new LinkedList<Entry<E>>();
		ArrayList<E> result = new ArrayList<E>();
		if (root != null)
			list.addLast(root);
		while (!list.isEmpty()) {
			Entry<E> p = list.removeFirst();
			result.add(p.element);
			if (p.left != null) {
				list.addLast(p.left);
			}
			if (p.right != null) {
				list.addLast(p.right);
			}
		}
		return result;
	}

	/**
	 * 使用宽度遍历逐层打印元素
	 * 
	 * @param root
	 *            根节点
	 * @return
	 */
	public static <E> String toString(Entry<E> root) {
		return breadthFirst(root).toString();
	}

输出结果为[a, b, c, d, e, f, g, h]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值