2021/3/6 算法训练 绘制地图(已知树的先序和中序求后序序列)

算法训练 绘制地图

【练习时间】2021/3/6(实际上应该是3/7-3/8号都在和这个题作斗争,在崩溃的边缘不断试探……)
【题目名称】 算法训练 绘制地图
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
  最近,WYF正准备参观他的点卡工厂。WYF集团的经理氰垃圾需要帮助WYF设计参“观”路线。现在,氰垃圾知道一下几件事情:
  1.WYF的点卡工厂构成一颗二叉树。
  2.一共有n座工厂。
  3.他需要把这颗树上的点以后序遍历的方法列出来,才能绘制地图。
  还好,最近他的属下给了他先序遍历和中序遍历的数据。可是,氰垃圾最近还要帮㊎澤穻解决一些问题,没有时间。请你帮帮他,替他完成这项任务。由于氰垃圾的一些特殊的要求,WYF的参观路线将会是这棵树的后序遍历。
输入格式
  第一行一个整数n,表示一共又n座工厂。
  第二行n个整数,表示先序遍历。
  第三行n个整数,表示中序遍历。
输出格式
  输出共一行,包含n个整数,为后序遍历。
样例输入
8
1 2 4 5 7 3 6 8
4 2 7 5 1 8 6 3
样例输出
4 7 5 2 8 6 3 1
数据规模和约定
  0<n<100000,。保证先序遍历和中序遍历合法,且均为1~n。

【题目分析】
简而言之,就是给你前序遍历和中序遍历的数组,让你输出后序遍历的结果,请注意,此题的数据量巨大,需要对程序进行优化,降低时间复杂度。

【解题过程】
历经两天折磨的时光,翻阅大量的解题思路,终于在三月八号这天早晨成功实现,啊啊啊啊啊!!!
在这里插入图片描述
下面我来说一下具体的解题思路到最终的代码优化:

  1. 根据前序数组和中序数组,求出根节点、左子树根节点、右子树根节点;

    (1)首先我们从前序数组当中读取元素,并查找其在中序数组当中的位置。

int FindNode(int num)
{
	for(int i=0;i<n;i++)
	{
		if(mid[i]==num)
		return i;
	}
	return -1;
} 

(2)以这个位置为中心把中序数组一分为两段,左边的数都在左子树上,右边的数都在右子树上。为了便于创建节点,我们设置了一个函数。

BiNode *CreateNode(int val)
{
	BiNode *node = new BiNode;
	node->data=val;
	node->rchild=NULL;
	node->lchild=NULL;
	return node;
}

(3)将这两段分别重复调用上述操作,每调用一次,前序数组当中的指向向后加一,优先调用左半段。

BiNode *CreatTree(int &cur_pos,int left,int right)//cur_pos代表指向前序数组的位置,left为中序数组的左边界,right为右边界
{
	if(cur_pos>n-1||left>right)
	{
		if(left>right) cur_pos-=1;
		return NULL;
	}
	if(left==right) return CreateNode(pre[cur_pos]);
	int val = pre[cur_pos];
	int pos = FindNode(val);
	BiNode *root=CreateNode(val);
	cur_pos+=1;
	root->lchild=CreatTree(cur_pos,left,pos-1);
	cur_pos+=1;
	root->rchild=CreatTree(cur_pos,pos+1,right);
	return root;
}
  1. 调用终止返回的条件
    (1)当前序数组走到边界时,或中序数组的右边界大于左边界时(此时前序数组指针减一后再返回NULL,目的是继续对此数操作,直到找到合适的位置),返回NULL;
    (2)当中序数组的左右边界相等时,他们都指向同一个数,则创建一个节点并返回。

【源代码1.0版】

#include <iostream>
using namespace std; 
int n=0,pre[100000],mid[100000];
struct BiNode 
{
	int data;
	BiNode *lchild;
	BiNode *rchild;
};
int FindNode(int num)
{
	for(int i=0;i<n;i++)
	{
		if(mid[i]==num)
		return i;
	}
	return -1;
} 
BiNode *CreateNode(int val)
{
	BiNode *node = new BiNode;
	node->data=val;
	node->rchild=NULL;
	node->lchild=NULL;
	return node;
}
BiNode *CreatTree(int &cur_pos,int left,int right)
{
	
	if(cur_pos>n-1||left>right)
	{
		if(left>right) cur_pos-=1;
		return NULL;
	}
	if(left==right) return CreateNode(pre[cur_pos]);
	int val = pre[cur_pos];
	int pos = FindNode(val);
	BiNode *root=CreateNode(val);
	cur_pos+=1;
	root->lchild=CreatTree(cur_pos,left,pos-1);
	cur_pos+=1;
	root->rchild=CreatTree(cur_pos,pos+1,right);
	return root;
}
void PostOrder(BiNode *bt)
{	
	if(bt==NULL)
		return;
	else{
		PostOrder(bt->lchild);
		PostOrder(bt->rchild);
		cout<<bt->data<<" ";
	} 
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>pre[i];
	for(int i=0;i<n;i++)
	cin>>mid[i];
	int cur_pos=0;
	BiNode *root=CreatTree(cur_pos,0,n-1);
	PostOrder(root);
	return 0;
}

提交后我们发现情况是这样滴……
在这里插入图片描述
运行超时的数据量都是十万,时间限制是1s我们超时了……没办法只能优化代码!!

【优化代码思路】
我认为造成超时的原因可能有以下几个方面:

  1. 在中序数组中查找元素时能不能尽量缩短查找范围?
  2. 有没有可能在不构建树的基础上后序输出?

现在说明以下解决上述问题的思路:

针对question1:
我们在FindNode函数中传入查找的起始位置和终止位置,起始位置就是中序数组的左边界,终止位置是中序数组的右边界。可大大缩短了查找量!

int FindNode(int num,int start,int end)//在中序数组的左右边界范围之内查找,如果有那么一定在此范围之内!!!
{
	for(int i=start;i<=end;i++)
	{
		if(mid[i]==num)
		return i;
	}
	return -1;
} 

针对question2:
我们不再构建树也不要将数据存放到节点当中,我们直接在函数中输出数据,后序输出的思路是:左子树——>右子树——>根节点;那么我们可以在左子树和右子树遍历完成后输出数据即可。

void CreatTree(int &cur_pos,int left,int right)//函数类型更改为void
{
	
	if(cur_pos>n-1||left>right)
	{
		if(left>right) cur_pos-=1;
		return;//直接返回即可
	}
	if(left==right) //当左右边界相遇时输出此数!
	{
		cout<<pre[cur_pos]<<" ";
		return;
	}
	int val = pre[cur_pos];
	int pos = FindNode(val,left,right);
	cur_pos+=1;
	CreatTree(cur_pos,left,pos-1);//遍历左子树
	cur_pos+=1;
	CreatTree(cur_pos,pos+1,right);//遍历右子树
	cout<<val<<" ";//输出根节点
	return;
}

【源代码2.0版】

#include <iostream>
using namespace std; 
int n=0,pre[100000],mid[100000];
int FindNode(int num,int start,int end)
{
	for(int i=start;i<=end;i++)
	{
		if(mid[i]==num)
		return i;
	}
	return -1;
} 
void CreatTree(int &cur_pos,int left,int right)
{
	if(cur_pos>n-1||left>right)
	{
		if(left>right) cur_pos-=1;
		return;
	}
	if(left==right) 
	{
		cout<<pre[cur_pos]<<" ";
		return;
	}
	int val = pre[cur_pos];
	int pos = FindNode(val,left,right);
	cur_pos+=1;
	CreatTree(cur_pos,left,pos-1);
	cur_pos+=1;
	CreatTree(cur_pos,pos+1,right);
	cout<<val<<" ";
	return;
}
int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	cin>>pre[i];
	for(int i=0;i<n;i++)
	cin>>mid[i];
	int cur_pos=0;
	CreatTree(cur_pos,0,n-1);
	return 0;
}

【运行结果】
在这里插入图片描述

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Yozu_Roo

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

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

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

打赏作者

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

抵扣说明:

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

余额充值