剑指Offer - 面试题7:重构二叉树 (力扣 - 105、从前序与中序遍历序列构造二叉树)

简介: 剑指Offer - 面试题7:重构二叉树 (力扣 - 105、从前序与中序遍历序列构造二叉树)

题目

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如:输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树如下图。返回该二叉树的头节点。

    3
   / \
  9  20
    /  \
   15   7

二叉树定义如下:

struct BinaryTreeNode
{
  int m_nValue;
  BinaryTreeNode* m_pLeft;
  BinaryTreeNode* m_pRight;
};

分析

递归

首先画出先序遍历和中序遍历的结构图,由此我们可以发现只要计算出根节点在中序中的位置,所有的位置都可以表示出来

假设我们通过遍历得到lNum

根的左子树区域为:先序 [preorder[1] ,preorder[1+lNum] ], 中序[inorder[0],inorder[lNum]];

根的右子树区域为:先序 [preorder[1+lNum] ,preorder[length-1]], 中序[inorder[lNum+1 ],inorder[length-1]];

因为先序和中序距离一样,我们可以只拿俩数组的首地址,和子树长度即可。

C

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<math.h>
#include<assert.h>
#define MAXSIZE 100 /* 存储空间初始分配量 */
typedef int Status;   /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int TElemType;  /* 树结点的数据类型,目前暂定为整型 */
struct BinaryTreeNode
{
  TElemType m_nValue;
  struct BinaryTreeNode* m_pLeft, * m_pRight;//左右孩子节点
};
void Print(TElemType n)
{
  printf("%d ", n);
}
void Preorder(BinaryTreeNode* root)//前序遍历
{
  if (NULL == root)
  {
    return;
  }
  Print(root->m_nValue);
  Preorder(root->m_pLeft);
  Preorder(root->m_pRight);
}
void Inorder(BinaryTreeNode* root)//中序输出
{
  if (root == NULL)
  {
    return;
  }
  Inorder(root->m_pLeft);
  Print(root->m_nValue);
  Inorder(root->m_pRight);
}
struct BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
  //参数非法 以及 递归结束条件
  if (NULL == preorder || NULL == inorder || length <= 0 )
  {
    return NULL;
  }
  int i = 0;      //用于遍历查找中序数组中根的位置
  int val = 0;    //当前节点的值
  int lNum = 0;   //左子树长度
  int rNum = 0;   //右子树长度
  struct BinaryTreeNode* node = (struct BinaryTreeNode*)malloc(sizeof(struct BinaryTreeNode));
  if (NULL == node)
  {
    perror("空间申请失败!\n");
    exit(EXIT_FAILURE);
  }
  val = preorder[0];
  //在中序数组中查找当前节点值的位置
  for (i = 0; i < length; i++)
  {
    if (val == inorder[i])
    {
      break;
    }
    lNum++;
  }
  rNum = length - lNum - 1;
  node->m_nValue = val;
  node->m_pLeft = Construct(&preorder[1], &inorder[0], lNum);
  node->m_pRight = Construct(&preorder[lNum + 1], &inorder[lNum + 1], rNum);
  return node;
}
int main()
{
  int pre[] = { 1,2,4,7,3,5,6,8 };
  int ino[] = { 4,7,2,1,5,3,8,6 };
  struct BinaryTreeNode * root = Construct(pre, ino, sizeof(ino) / sizeof(ino[0]));
  printf("先序输出:");
  Preorder(root);
  printf("\n中序输出:");
  Inorder(root);
  return 0;
}

构造好二叉树后,再次输出验证。

C++可以用哈希表,拿空间换时间

参数较多:

pleft和pright分别为前序遍历的左右闭区间。

ileft和 iright分别为中序遍历的左右闭区间。

通过前序遍历和pleft找到当前根节点。再通过哈希表找到该节点在中序遍历的位置index。进而可以求出左子树的长度lNum = index - ileft。

需要注意一点:只有index是绝对位置(按照最开始的哈希表做的),边界下标都会收缩的。


include<iostream>
#include<map>
using namespace std;
#define MAXSIZE 100   /* 存储空间初始分配量 */
typedef int Status;   /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int TElemType;  /* 树结点的数据类型,目前暂定为整型 */
class BinaryTreeNode
{
public:
  TElemType m_nValue;
  BinaryTreeNode* m_pLeft, * m_pRight;//左右孩子节点
public:
  BinaryTreeNode();
  BinaryTreeNode(TElemType x);
  ~BinaryTreeNode();
};
BinaryTreeNode::BinaryTreeNode()
{
  m_nValue = 0;
  m_pLeft = NULL;
  m_pRight = NULL;
}
BinaryTreeNode::BinaryTreeNode(TElemType x)
{
  m_nValue = x;
  m_pLeft = NULL;
  m_pRight = NULL;
}
BinaryTreeNode::~BinaryTreeNode()
{
}
void Print(TElemType n)
{
  cout << n << " ";
}
void CreatTree(BinaryTreeNode** T)
{
  TElemType elem;
  cin >> elem;
  if (elem != 999)
  {
    *T = new BinaryTreeNode();
    if (NULL == T)
    {
      perror("空间申请失败!\n");
      exit(EXIT_FAILURE);
    }
    (*T)->m_nValue = elem;
    CreatTree(&((*T)->m_pLeft));
    CreatTree(&((*T)->m_pRight));
  }
  else
  {
    *T = NULL;
  }
}
void Preorder(BinaryTreeNode* root)//前序遍历
{
  if (NULL == root)
  {
    return;
  }
  Print(root->m_nValue);
  Preorder(root->m_pLeft);
  Preorder(root->m_pRight);
}
void Inorder(BinaryTreeNode* root)//中序输出
{
  if (root == NULL)
  {
    return;
  }
  Inorder(root->m_pLeft);
  Print(root->m_nValue);
  Inorder(root->m_pRight);
}
BinaryTreeNode* ConstructCore(int* preorder, int* inorder, 
  int preorder_left, int preorder_right, int inorder_left, 
  int inorder_right, map<TElemType, int> m)
{
  //参数非法 以及 递归结束条件
  if (NULL == preorder || NULL == inorder || 
    preorder_left < 0 || preorder_right < preorder_left || 
    inorder_left < 0 ||inorder_right < inorder_left)
  {
    return NULL;
  }
  // 前序遍历中的第一个节点就是根节点preorder[preorder_index]
  // 按前序遍历preorder_left下标的值作为key找到哈希表对应的value
  // 即中序遍历中该节点的下标
  int inorder_index = m[preorder[preorder_left]];
  BinaryTreeNode* node = new BinaryTreeNode(preorder[preorder_left]);
  //左子树中的节点数目
  int lNum = inorder_index - inorder_left;
  node->m_pLeft = ConstructCore(preorder, inorder, preorder_left + 1, 
      preorder_left + lNum, inorder_left, inorder_index - 1, m);
  node->m_pRight = ConstructCore(preorder, inorder, preorder_left + 1 + lNum,
    preorder_right, 1 + inorder_index, inorder_right, m);
  return node;
}
BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
  //参数非法
  if (NULL == preorder || NULL == inorder || length <= 0)
  {
    return NULL;
  }
  map<TElemType, int> m;
  int i = 0;
  for (i = 0; i < length; i++)
  {
    m.insert(make_pair(inorder[i], i));
  }
  return ConstructCore(preorder, inorder, 0, length - 1, 0, length - 1 , m);
}
int main()
{
  int pre[] = { 1,2,4,7,3,5,6,8 };
  int ino[] = { 4,7,2,1,5,3,8,6 };
  BinaryTreeNode* root = Construct(pre, ino, sizeof(ino) / sizeof(ino[0]));
  cout << "先序遍历:";
  Preorder(root);
  cout << endl << "中序遍历:";
  Inorder(root);
  return 0;
}


迭代

我们可以用一个栈来保存每次遍历的节点。先把根节点preorder[0]放入,然后遍历先序数组,从下标1开始,因为根节点提前创建。再定义一个index用来指向中序数组

  • 若发现栈顶指针指向的值不等于中序指针指向的值,那么说明,先序指向的值为前一个节点的左儿子。如下图

  • 若发现栈顶指针指向的值等于中序指针指向的值。那么说明左儿子已经完了。那么我们需要更新中序指针。只要栈不空且栈中的值等于当前中序指针指向的值。就一直循环。

    当不相等时,就说明接下来的值就是前一个节点的右儿子。所以我们在跟新中序指针时,还需要更新保存栈中的栈顶指针。见下图

C++

#include<iostream>
#include<stack>
using namespace std;
#define MAXSIZE 100   /* 存储空间初始分配量 */
typedef int Status;   /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int TElemType;  /* 树结点的数据类型,目前暂定为整型 */
class BinaryTreeNode
{
public:
  TElemType m_nValue;
  BinaryTreeNode* m_pLeft, * m_pRight;//左右孩子节点
public:
  BinaryTreeNode();
  BinaryTreeNode(TElemType x);
  ~BinaryTreeNode();
};
BinaryTreeNode::BinaryTreeNode()
{
  m_nValue = 0;
  m_pLeft = NULL;
  m_pRight = NULL;
}
BinaryTreeNode::BinaryTreeNode(TElemType x)
{
  m_nValue = x;
  m_pLeft = NULL;
  m_pRight = NULL;
}
BinaryTreeNode::~BinaryTreeNode()
{
}
void Print(TElemType n)
{
  cout << n << " ";
}
void CreatTree(BinaryTreeNode** T)
{
  TElemType elem;
  cin >> elem;
  if (elem != 999)
  {
    *T = new BinaryTreeNode();
    if (NULL == T)
    {
      perror("空间申请失败!\n");
      exit(EXIT_FAILURE);
    }
    (*T)->m_nValue = elem;
    CreatTree(&((*T)->m_pLeft));
    CreatTree(&((*T)->m_pRight));
  }
  else
  {
    *T = NULL;
  }
}
void Preorder(BinaryTreeNode* root)//前序遍历
{
  if (NULL == root)
  {
    return;
  }
  Print(root->m_nValue);
  Preorder(root->m_pLeft);
  Preorder(root->m_pRight);
}
void Inorder(BinaryTreeNode* root)//中序输出
{
  if (root == NULL)
  {
    return;
  }
  Inorder(root->m_pLeft);
  Print(root->m_nValue);
  Inorder(root->m_pRight);
}
BinaryTreeNode* Construct(int* preorder, int* inorder, int length)
{
  if (preorder == NULL || inorder == NULL || length <= 0)
  {
    return 0;
  }
  stack<BinaryTreeNode*> s;
  BinaryTreeNode* root = new BinaryTreeNode(preorder[0]);
  s.push(root);
  int inorder_index = 0;//中序遍历的指针
  for (int i = 1; i < length; i++)//每次循环连接一个节点,还需要连接length-1个。
  {
    BinaryTreeNode* node = s.top();
    if (node->m_nValue != inorder[inorder_index])//不相等,就是左儿子
    {
      node->m_pLeft = new BinaryTreeNode(preorder[i]);
      s.push(node->m_pLeft);
    }
    else
    {
      while (s.empty() != true && s.top()->m_nValue == inorder[inorder_index])
      {
        s.pop();
        node = s.top();//更新父节点
        inorder_index++;//中序遍历指针移动
      }
      //此时i和inorder_index均指向右节点。都可以使用inorder[inorder_index]
      node->m_pRight = new BinaryTreeNode(preorder[i]);
      s.push(node->m_pLeft);
    }
  }
  return root;
}
int main()
{
  int pre[] = { 1,2,4,7,3,5,6,8 };
  int ino[] = { 4,7,2,1,5,3,8,6 };
  BinaryTreeNode* root = Construct(pre, ino, sizeof(ino) / sizeof(ino[0]));
  cout << "先序遍历:";
  Preorder(root);
  cout << endl << "中序遍历:";
  Inorder(root);
  return 0;
}


本章完!

目录
相关文章
|
3月前
|
算法
二叉树面试题
本文介绍了二叉树相关的几个经典算法问题。首先讲解了如何判断两棵树是否完全相同(LeetCode 100),并给出了代码示例。接着讨论了如何判断一棵树是否是另一棵树的子树(LeetCode 572),详细分析了子树的定义及判断方法。然后介绍了翻转二叉树(LeetCode 226)的实现方法,即在遍历时交换每个节点的左右子树。随后探讨了如何判断一棵树是否是对称的(LeetCode 101),通过对左右子树的递归比较来实现。最后分析了平衡二叉树的概念(LeetCode 110)及判断方法,并给出了优化后的代码示例。此外,还简要介绍了二叉树遍历及二叉树最近公共祖先(LeetCode 236)的问题
31 6
二叉树面试题
|
2月前
【LeetCode 31】104.二叉树的最大深度
【LeetCode 31】104.二叉树的最大深度
26 2
|
2月前
【LeetCode 29】226.反转二叉树
【LeetCode 29】226.反转二叉树
20 2
|
2月前
【LeetCode 28】102.二叉树的层序遍历
【LeetCode 28】102.二叉树的层序遍历
19 2
|
1月前
|
算法 Java
JAVA 二叉树面试题
JAVA 二叉树面试题
19 0
|
2月前
【LeetCode 43】236.二叉树的最近公共祖先
【LeetCode 43】236.二叉树的最近公共祖先
21 0
|
2月前
【LeetCode 38】617.合并二叉树
【LeetCode 38】617.合并二叉树
15 0
|
2月前
【LeetCode 37】106.从中序与后序遍历构造二叉树
【LeetCode 37】106.从中序与后序遍历构造二叉树
23 0
|
2月前
【LeetCode 34】257.二叉树的所有路径
【LeetCode 34】257.二叉树的所有路径
22 0
|
2月前
【LeetCode 32】111.二叉树的最小深度
【LeetCode 32】111.二叉树的最小深度
19 0