剑指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++可以用哈希表,拿空间换时间
参数较多:
pleftpright分别为前序遍历的左右闭区间。
ileftiright分别为中序遍历的左右闭区间。
通过前序遍历和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;
}

在这里插入图片描述
本章完!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林夕07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值