首先来个具体的问题吧,因为只是空谈显示不出效果。
如题
:
求先序排列
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度<=8)。
输入格式
两行,每行一个字符串,分别表示中序和后序排列
输出格式
一个字符串,表示所求先序排列
样例输入
BEADC
EBDCA
样例输出
ABECD
解题思路
关于这题其实解体思路很多,但是目前我发现最简单的只有一种。
先从一种最常规的解法思路说起吧,不要心急,这种解法与最简解法也是思路相近的。
对于这题,我们知道它的先序和中序序列,以题目中的样例为例,我们可以很容易的通过后序找到它的根节点A,并且由中序序列得知B、E是A的左子树内容,D、C是A的右子树内容。
那么要得到想要的先序序列,就必须继续确定左右子树的根节点,很显然这是一个递归逻辑。
停止递归的条件是左右子树的节点全部输出。
确定根节点,最简单的一种思路就是用for循环来查找,比如我们确定了A的左子树,有B,E两个节点,然后我们要查找左子树的根节点,只需要找到左子树在后序序列中最靠右的元素就好啦。
写起来就两个for循环,一个用来遍历后序数组,一个用来挨个判断是否是左子树的节点。然后用个变量存储根节点。
这样就可以不断的用递归思路求解根节点,然后输出,直至左右子树与根节点重合,结束。
代码如下:
#include <iostream>
#include <string>
using namespace std;
string s1, s2;
void dfs(int l1, int r1, int l2, int r2)
{
cout << s1[r1+1];
if (r1 >=l1)
{
int poi = 0;
for (int i = 0; i < s2.length(); i++)
{
for (int j = l1; j <=r1; j++)
{
if (s2[i] == s1[j])
{
poi = j;
break;
}
}
}
dfs(l1, poi-1, poi+1, r1);
}
if(r2>=l2)
{
int poi = 0;
for (int i = 0; i < s2.length(); i++)
{
for (int j = l2; j <=r2; j++)
{
if (s2[i] == s1[j])
{
poi = j;
break;
}
}
}
dfs(l2, poi-1, poi + 1, r2);
}
}
int main()
{
cin >> s1 >> s2;
int ma = s1.length() - 1;
int d = s1.find(s2[ma]);
dfs(0, d-1, d+1, ma);
return 0;
}
接下来的,是一种更优的算法,我们已经知道了求解先序序列的基本思路。
但是,如果使用上面的方法,时间复杂度为(nlogn)。
中间的for循环查找会造成大量的时间浪费,因为仔细研究后序序列和中序序列,他们是可以找到一些规律的。
先给出下图
仔细看的话(看图就好,下面可以忽略)可以发现,后序遍历中根节点排布是有一定规律的。
后序序列中最靠右的节点就是子树的根节点。
我们之前使用的方法是从前面一个一个查找,但是其实如果我们假设中序和后序序列的左端和右段下标分别为了l1,r1,l2,r2,并且中序遍历根节点位置为poi。
我们就可以轻松地得到它的左子树以及右子树的根节点位置,而且还有两种表示方法,一种是从左边考虑,另一种从右边考虑。
比如表示后序序列左子树的根节点的位置,可以是l2+(poi-l1)-1,也可以是r2-(r1-poi)-1.
那么写出来的代码当然也可以是两种,不过思路其实相同就是。
参考代码:
#include <iostream>
#include <string>
using namespace std;
string s1, s2;
void dfs(int l1, int r1, int l2, int r2)
{
cout << s2[r2];//后序序列找到的根节点
int d = s1.find(s2[r2]);
if (d > l1)
{
dfs(l1, d - 1, l2, l2+(d-l1)-1);
}
if (d < r1)
{
dfs(d + 1, r1, r2-(r1-d), r2 - 1);
}
}
int main()
{
cin >> s1 >> s2;
int ma = s1.length() - 1;
dfs(0, ma, 0, ma);
return 0;
}