九度OJ-题目1385:重建二叉树

题目链接地址:

九度OJ-题目1385:重建二叉树


题目描述:
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并输出它的后序遍历序列。
九度OJ-题目1385:重建二叉树_第1张图片


输入:
输入可能包含多个测试样例,对于每个测试案例,
输入的第一行为一个整数n(1<=n<=1000):代表二叉树的节点个数。
输入的第二行包括n个整数(其中每个元素a的范围为(1<=a<=1000)):代表二叉树的前序遍历序列。
输入的第三行包括n个整数(其中每个元素a的范围为(1<=a<=1000)):代表二叉树的中序遍历序列。

输出:
对应每个测试案例,输出一行:
如果题目中所给的前序和中序遍历序列能构成一棵二叉树,则输出n个整数,代表二叉树的后序遍历序列,每个元素后面都有空格。
如果题目中所给的前序和中序遍历序列不能构成一棵二叉树,则输出”No”。

样例输入:
8
1 2 4 7 3 5 6 8
4 7 2 1 5 3 8 6
8
1 2 4 7 3 5 6 8
4 1 2 7 5 3 8 6

样例输出:
7 4 2 5 8 6 3 1
No


解题思路:

如果不算层序遍历的话,二叉树一共有3种遍历方式。
分别是前序遍历,中序遍历,后序遍历。
如果在遍历二叉树的过程中,约定先遍历完左子树后,再遍历右子树。
那么前序遍历的遍历顺序是根左右,中序遍历的遍历顺序是左根右,后序遍历的遍历顺序是左右根。(左代表左子树,右代表右子树,根代表根结点)
因为通过前序遍历(或者后序遍历)可以找到二叉树的根结点,再根据根结点在中序遍历序列中的位置就可以确定根结点的左右子树;所以给定某棵二叉树的中序遍历序列和前序遍历序列(或者后序遍历序列)就能重新构造出该二叉树。
因为二叉树是一种递归结构,也就是说二叉树的左右子树也都是二叉树。所以我们可以采用递归的思路将问题转化为本质相同但是规模更小的子问题。因为二叉树是一种递归的数据结构,所以我们可以先将每个结点都看成是一棵只有根结点而左右子树都为NULL的二叉树,然后根据前序和中序遍历序列确定各个结点之间的父子关系就能重构出二叉树。
具体的重构步骤如下:
(1) 首先将二叉树中的每个结点都看成是一棵只有根结点而左右子树都为NULL的二叉树;
(2) 然后将前序遍历序列中的元素依次赋值给二叉树每个结点,显然前序遍历序列第一个元素所对应的结点就是重构二叉树的根结点;
(3) 寻找前序遍历序列第一个元素在中序遍历序列中的位置,如果中序遍历序列不包含前序遍历序列第一个元素则表明无法重构二叉树;否则将前序遍历序列第一个元素添加到重构二叉树中,再以该元素在中序遍历序列中的位置为分隔点,将中序遍历序列分成左右两个子序列,其中左子序列对应着根结点的左子树的中序遍历序列,右子序列对应着右子树的中序遍历序列。然后采用递归的方法继续重构根结点所对应的左子树和右子树,直至所有结点都已出现在重构二叉树中。

下面以第一个测试用例为例具体讲解一下解题思路:
前序遍历为 1 2 4 7 3 5 6 8
中序遍历为 4 7 2 1 5 3 8 6
通过查看前序遍历序列可以知道二叉树的根结点为1,然后在中序遍历序列中查找1的位置,以1为分割点,中序遍历序列中出现在1左边的子序列就是根结点1的左子树的中序遍历序列,出现在1右边的子序列就是根结点1的左子树的中序遍历序列。因此可以得到以下结果:
由前序遍历序列可得,重构二叉树的根结点为1,左子树前序遍历序列为2 4 7,右子树的前序遍历序列为3 5 6 8,根结点1的左孩子结点是2,右孩子结点是3。
由中序遍历序列可得,4 7 2是根结点1的左子树的中序遍历序列,5 3 8 6是根结点1的右子树的中序遍历序列。
采用递归的思路继续重构左子树,此时问题的本质不变,但是问题的规模从重构含有8个结点的二叉树变成了只含有3个结点,前序遍历序列为2 4 7,中序遍历序列为4 7 2的子问题。采用同样的思路重构右子树,待所有的结点都已经添加到二叉树中后,重构二叉树的操作就完成了。
具体构造过程如图1所示:

九度OJ-题目1385:重建二叉树_第2张图片

图1 根据前序遍历和中序遍历重构二叉树的过程


下面以第二个测试用例为例具体讲解一下题目中所给的前序和中序遍历序列不能构成一棵二叉树的情况:
前序遍历为 1 2 4 7 3 5 6 8
中序遍历为 4 1 2 7 5 3 8 6
通过查看前序遍历序列可以知道二叉树的根结点为1,然后在中序遍历序列中查找1的位置,以1为分割点,可以得到以下结果:
重构二叉树的根结点为1,左子树的前序遍历序列是2,中序遍历序列是4;右子树的前序遍历序列是4 7 3 5 6 8,中序遍历序列2 7 5 3 8 6。
根据前序遍历序列可以得出该二叉树的大致结构如图2所示:

九度OJ-题目1385:重建二叉树_第3张图片

图2 根据前序遍历序列得到的二叉树的大致结构

根据中序遍历序列可以得出该二叉树的大致结构如图3所示:


图3 根据中序遍历序列得到的二叉树的大致结构

从图2和图3可以看出前序遍历序列和中序遍历序列生成的二叉树结构不相同,显示根据这个前序和中序遍历序列是不能构成一棵二叉树的。因此当中序遍历序列不包含前序遍历序列的某个元素时,我们就可以断定根据这个前序和中序遍历序列是无法构成一棵二叉树的。

AC代码如下:

#include<stdio.h>
#define MAX 1001
 
// 构建二叉树的结点
typedef struct Node
{
   int data;            // 数据域
   Node * lChild;       // 左子树
   Node * rChild;       // 右子树
}BinaryTreeNode;
BinaryTreeNode biTreeNode[MAX];
 
bool canRebuildBinaryTree; // 判断能否重构二叉树
int preOrder[MAX];      // 二叉树的前序遍历序列
int inOrder[MAX];       // 二叉树的中序遍历序列
 
/**
* 初始化二叉树中的每个结点,将每个结点都看成是只有一个根结点而左右子树都为NULL的二叉树
* @param int n  二叉树的结点数
* @return void
*/
void initBinaryTree(int n)
{
  int i;
  canRebuildBinaryTree = true;
  for(i = 0;i < n;i++)
  {
    biTreeNode[i].data = preOrder[i];  // 将前序遍历序列中的元素依次赋值给二叉树中的结点
    biTreeNode[i].lChild = NULL;
    biTreeNode[i].rChild = NULL;
  }
}
 
/**
* 重构二叉树
* @param int beginPreOrder  前序遍历序列的起点
* @param int endPreOrder  前序遍历序列的终点
* @param int beginInOrder  中序遍历序列的起点
* @param int endInOrder  中序遍历序列的终点
* @return void
*/
void reBuildBinaryTree(int beginPreOrder,int endPreOrder,int beginInOrder,int endInOrder)
{
   int i;
   int partion = -1;     // 前序遍历序列第一个结点在中序遍历序列中的相对位置
   bool isBeginPreOrderinInOrder = false; // 判断前序遍历序列中的第一个结点是否在中序遍历序列中,以此来判断是否能构成二叉树
   // 遍历二叉树的中序遍历序列,得到根结点在中序遍历序列中的位置
   for(i = beginInOrder;i <= endInOrder;i++)
   {
       if(preOrder[beginPreOrder] == inOrder[i])
       {
          partion = i - beginInOrder;
          isBeginPreOrderinInOrder = true;
          break;
       }
   }
   if(false == isBeginPreOrderinInOrder)
   {
       canRebuildBinaryTree = false;
       return;
   }
   else
   {
       // 以partion为分隔标识
       // 将前序遍历序列分为[beginPreOrder + 1,beginPreOrder + partion]
       // 和[beginPreOrder + partion + 1,endPreOrder]两个子序列
       // 将中序遍历序列分为[beginInOrder,beginInOrder + partion - 1]
       // 和[beginInOrder + partion + 1,endInOrder]两个子序列
 
       // 用前序遍历子序列[beginPreOrder + 1,beginPreOrder + partion]
       // 和中序遍历子序列[beginInOrder,beginInOrder + partion - 1]重构左子树
       if(beginPreOrder + 1 <= beginPreOrder + partion && beginInOrder <= beginInOrder + partion - 1)
       {
           biTreeNode[beginPreOrder].lChild = &biTreeNode[beginPreOrder + 1];
           reBuildBinaryTree(beginPreOrder + 1,beginPreOrder + partion,beginInOrder,beginInOrder + partion - 1);
       }
       // 用前序遍历子序列[beginPreOrder + partion + 1,endPreOrder]
       // 和中序遍历子序列[beginInOrder + partion + 1,endInOrder]重构右子树
       if(beginPreOrder + partion + 1 <= endPreOrder && beginInOrder + partion + 1 <= endInOrder)
       {
           biTreeNode[beginPreOrder].rChild = &biTreeNode[beginPreOrder + partion + 1];
           reBuildBinaryTree(beginPreOrder + partion + 1,endPreOrder,beginInOrder + partion + 1,endInOrder);
       }
   }
}
 
/**
* 后序遍历二叉树
* @param BinaryTreeNode * root  后序遍历的根结点
* @return void
*/
void postOrder(BinaryTreeNode * root)
{
  if(NULL == root)
    return;
  else
  {
     postOrder(root -> lChild);
     postOrder(root -> rChild);
     printf("%d ",root -> data);
  }
}
 
int main()
{
    int n;
    int i;
    while(EOF != scanf("%d",&n))
    {
      for(i = 0;i < n;i++)
      {
          scanf("%d",&preOrder[i]);
      }
      for(i = 0;i < n;i++)
      {
          scanf("%d",&inOrder[i]);
      }
      initBinaryTree(n);
      reBuildBinaryTree(0,n - 1,0,n - 1);
      if(false == canRebuildBinaryTree)
         printf("No\n");
      else
      {
         postOrder(&biTreeNode[0]); //因为 biTreeNode[0].data == preOrder[0],所以biTreeNode[0]是重构二叉树的根结点
         printf("\n");
      }
    }
    return 0;
}
 
/**************************************************************
    Problem: 1385
    User: blueshell
    Language: C++
    Result: Accepted
    Time:0 ms
    Memory:1052 kb
****************************************************************/


你可能感兴趣的:(面试题,剑指offer)