剑指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;
}


本章完!

目录
相关文章
|
2月前
|
存储 算法
《LeetCode》—— 摆动序列
《LeetCode》—— 摆动序列
|
7天前
[leetcode~dfs]1261. 在受污染的二叉树中查找元素
[leetcode~dfs]1261. 在受污染的二叉树中查找元素
[leetcode~dfs]1261. 在受污染的二叉树中查找元素
|
14天前
|
算法 API DataX
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
二叉树(下)+Leetcode每日一题——“数据结构与算法”“对称二叉树”“另一棵树的子树”“二叉树的前中后序遍历”
|
14天前
|
算法 DataX
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
|
15天前
|
算法 定位技术
【leetcode】剑指 Offer II 105. 岛屿的最大面积-【深度优先DFS】
【leetcode】剑指 Offer II 105. 岛屿的最大面积-【深度优先DFS】
17 0
|
16天前
|
算法
【力扣】94. 二叉树的中序遍历、144. 二叉树的前序遍历、145. 二叉树的后序遍历
【力扣】94. 二叉树的中序遍历、144. 二叉树的前序遍历、145. 二叉树的后序遍历
|
2月前
|
Java 程序员
java线程池讲解面试
java线程池讲解面试
62 1
|
3月前
|
存储 关系型数据库 MySQL
2024年Java秋招面试必看的 | MySQL调优面试题
随着系统用户量的不断增加,MySQL 索引的重要性不言而喻,对于后端工程师,只有在了解索引及其优化的规则,并应用于实际工作中后,才能不断的提升系统性能,开发出高性能、高并发和高可用的系统。 今天小编首先会跟大家分享一下MySQL 索引中的各种概念,然后介绍优化索引的若干条规则,最后利用这些规则,针对面试中常考的知识点,做详细的实例分析。
253 0
2024年Java秋招面试必看的 | MySQL调优面试题
|
3月前
|
存储 算法 Java
铁子,你还记得这些吗----Java基础【拓展面试常问题型】
铁子,你还记得这些吗----Java基础【拓展面试常问题型】
50 1
|
3月前
|
NoSQL Java 关系型数据库
凭借Java开发进阶面试秘籍(核心版)逆流而上
最近参加了面试或者身边有朋友在面试的兄弟有没有发现,现在的面试不仅会问八股文,还会考察框架、项目实战、算法数据结构等等,需要准备的越来越多。 其实面试的时候,并不是要求你所有的知识点都会,而是关键的问题答到点子上!这份《Java 开发进阶面试秘籍(核心版)》由 P8 面试官整体把控,目前已经更新了 30 万字! 资料中涵盖了一线大厂、中小厂面试真题,毕竟真题都是技术领域最经典的基础知识和经验沉淀的汇总,非常有必要学习掌握!双重 buff 叠加,offer 接到手软~ 点击此处取,这可能是你到目前为止领取的最具含金量的一份资料! 整套资料涵盖:Spring、Spring