Problem318二叉树遍历
题面
给定一棵二叉树的先序遍历和中序遍历序列,求其后序遍历序列。
解答
用递归的方式进行求解。 显而易见,任何一个子树的先序遍历序列的首字母一定是该子树的树根。而在中序遍历中,该树根左侧是左子树,右侧是右子树,那么我们只需要不断的递归分解左右子树,即可求出原树。 需要注意的是,由于我们需要求后序遍历序列,因此对于每一棵子树,都需要在它左右子树分别递归分解完成之后再输出树根结点。
code
void getnxt (string pre, string mid, int len) {
if (len == 0) return ;
if (len == 1) {
cout << pre;
return ;
}
int k = mid.find (pre[0]);
getnxt (pre.substr (1, k), mid.substr (0, k), k);
getnxt (pre.substr (k + 1, len - 1 - k), mid.substr (k + 1, len - 1 - k), len - k - 1);
cout << pre[0];
}
这里的string的substr函数共有两个参数,例如pre.substr(1,k),意思是在取string类型pre的子串[1…k](从下标1处开始,长度为k)
Problem319将满二叉树转化为求和树
题面
解答
大致思路和Problem318一样,细节处理上需要花一些功夫。 这里,我们getnxt函数设置为int类型,以返回当前结点的左右子树之和。我们需要知道的是,对于每一个结点,它的求和树上对应的结点数值是它左右子树之和(不包括它本身),因此我们这里需要先把它本身的数值记录下来,再赋予它新值。特别地,对于每一个叶子结点,在将它的值返回上一层函数之前,需要将它赋零。 经过我们的操作,原中序遍历序列就成了答案要求的求和树中序遍历序列。
code
int find (int L, int R, int x) {
for (int i = L;i <= R;i ++)
if (b[i] == x) return i;
}
int getnxt (int l, int r, int L, int R) {
int len = r - l + 1;
if (len == 0) return 0;
if (len == 1) {
int tmp = a[l];
a[l] = b[L] = 0;
return tmp;
}
int k = find (L, R, a[l]);
int t1 = getnxt (l + 1, l + k - L, L, k - 1);
int t2 = getnxt (l + k - L + 1, r, k + 1, r);
int tmp = a[l];
a[l] = b[k] = t1 + t2;
return tmp + a[l];
}
Problem320二叉树的不同形态
题面
解答
这道题相对前两道题较复杂。一共分为两个步骤
- 根据层次遍历序列和中序遍历序列求出先序遍历序列
- 根据先序遍历序列和中序遍历序列求出后序遍历序列
第二步是前面介绍过的算法。这里着重介绍第一步的算法。 直观暴力思想 还是同之前一样,利用递归的手段进行左右子树拆解。但是不同的是,层次遍历序列不像先序遍历序列那样,可以非常容易得知每一棵子树的根节点。但是我们可以发现两个性质:
- 在层次遍历序列中,父亲结点永远出现在儿子节点的前面。
- 在层次遍历序列中,在父亲结点后出现的第一个存在于左子树的结点,就是该父亲节点的左子树根节点;右子树根节点亦然。
所以我们可以改进之前的算法: 若左子树不为空,当遍历左子树前,我们可以通过查找层次遍历序列中,父亲节点后,第一个出现的存在于左子树的结点,作为左子树的根节点,以它为依据划分中序遍历序列,继续拆解二叉树。
if (k - l > 0) {
int t = root + 1;
while (find (l, k - 1, dep[t]) == -1) t ++;
getpre (l, k - 1, t);
}
但是我们发现了一个问题,在寻找一个父亲节点的两个孩子结点时候,需要对层次遍历序列中,父亲孩子之间的所有结点都进行while (find (l, k - 1, dep[t]) == -1) t ++; 的操作,粗略估计下时间复杂度大约是
O
(
n
)
=
n
3
O(n)=n^3
O(n)=n3级别,算法还需要改进。我们很容易意识到一个特性:
- 当一个结点在拆解过程中被作为父亲结点使用后,一定不会使用第二次。
因此我们可以设置一个新函数(其实就是一个bool类型的标记数组)used[x]。当x没有被当做父亲节点使用时,我们让他为0,代表它需要被查找;一旦被使用后让他为1,代表他不需要再被查找。于是代码可以改为:
if (k - l > 0) {
int t = root + 1;
while (used[t] || find (l, k - 1, dep[t]) == -1) t ++;
used[t] = 1;
getpre (l, k - 1, t);
}
该算法在满二叉树的情况下效果最差,但是在题目的数据范围下经过自造数据的多次检验,可以在1s内完美跑完。
code
int find (int l, int r, int x) {
for (int i = l;i <= r;i ++)
if (mid[i] == x) return i;
return -1;
}
int pre[maxn], top = 0;
int used[maxn];
void getpre (int l, int r, int root) {
int len = r - l + 1;
if (len == 0) return;
if (len == 1) {
printf ("%d ", mid[l]);
pre[++ top] = mid[l];
return ;
}
int k = find (l, r, dep[root]);
pre[++ top] = mid[k];
if (k - l > 0) {
int t = root + 1;
while (used[t] || find (l, k - 1, dep[t]) == -1) t ++;
used[t] = 1;
getpre (l, k - 1, t);
}
if (r - k > 0) {
int t = root + 1;
while (used[t] || find (k + 1, r, dep[t]) == -1) t ++;
used[t] = 1;
getpre (k + 1, r, t);
}
}
|