PAT 1119. Pre- and Post-order Traversals (30) (根据前序后序求中序)

本文探讨了通过前序和后序遍历来判断二叉树中序遍历唯一性的方法。详细介绍了如何根据两种遍历方式确定二叉树结构,并提供了一个具体的实现案例。

题意:给你一棵树的前序和后序遍历,问你它的中序遍历是否唯一,并且给出任意一种中序遍历。


思路:判断中序遍历是否唯一,我们可以通过每个非叶子节点是否都是有两个孩子,如果只有一个孩子,那他的子树既可以是左子树又可以是又子树,所以就不唯一了。


那怎么判断是否有只有一个孩子的结点。

可以看看这个博客:点击打开链接

已知前序遍历和后序遍历序列,是无法确定一棵二叉树的,原因在于如果只有一棵子树可能是左孩子也有可能是右孩子。由于只要输出其中一个方案,所以假定为左孩子即可。下面就是如何根据前序和后序划分出根节点和左右孩子,这里需要定义前序和后序的区间范围,分别为[preL,preR],[postL,postR]。  

  一开始区间都为[1,n],可以发现前序的第一个和后序的最后一个为根节点root,前序的第二个值val为其某子树的根节点(但还无法确定是左孩子or右孩子)。在后序中找对应的值所在的位置postIdx,则postIdx之前的节点均为val的孩子节点,统计其个数num。那么我们就可以划分区间:  

若num个数=preR-preL-1,即val后面的个数都是其子节点,那么二叉树不唯一,将其作为root的左子树处理。

否则划分为左子树区间和右子树对应的前序和后序区间,顺便更新下root的左孩子preL+1,右孩子preL+num+2:
preOrder:[preL+1,preL+num+1],postOrder:[postL,postIdx];
preOrder:[preL+num+2,preR],postOrder:[postIdx+1,postR-1];
然后递归划分即可


拿样例举例:
1 (2) [3 {4 6 7} <5>]
(2) [{6 7 4} <5> 3] 1
不同的括号对应不同的子树区间
第一次递归划分了(2)-(2),[3 4 6 7 5]-[6 7 4 5 3]
由于(2)只有一棵,不继续划分。
第二次递归划分了{4 6 7}-{6 7 4},<5>-<5>
第三次递归划分了(6)-(6),(7)-(7)


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 35;
int n, cnt, pre[maxn], in[maxn], post[maxn];
bool isUnique;

void build(int preL, int preR, int postL, int postR)
{
    if(preL == preR) { in[cnt++] = pre[preL]; return ; }
    if(preL > preR) return ;
    int idx;
    for(int i = postL; i < postR; i++)
    {
        if(pre[preL+1] == post[i])
        {
            idx = i;
            break;
        }
    }
    int numL = idx-postL;
    if(preR-preL-1 == numL) isUnique = 0;
    build(preL+1, preL+1+numL, postL, idx);
    in[cnt++] = pre[preL];
//    cout << pre[preL] << endl;
    build(preL+numL+2, preR, idx+1, postR-1);
}

int main(void)
{
    while(cin >> n)
    {
        cnt = 1;
        isUnique = 1;
        for(int i = 1; i <= n; i++) scanf("%d", &pre[i]);
        for(int i = 1; i <= n; i++) scanf("%d", &post[i]);
        build(1, n, 1, n);
        puts(isUnique ? "Yes" : "No");
        for(int i = 1; i <= n; i++) printf("%d%c", in[i], i==n ? '\n' : ' ');
    }
    return 0;
}


American Heritage 题目描述 Farmer John takes the heritage of his cows very seriously. He is not, however, a truly fine bookkeeper. He keeps his cow genealogies as binary trees and, instead of writing them in graphic form, he records them in the more linear tree in-order" and tree pre-order" notations. Your job is to create the `tree post-order" notation of a cow"s heritage after being given the in-order and pre-order notations. Each cow name is encoded as a unique letter. (You may already know that you can frequently reconstruct a tree from any two of the ordered traversals.) Obviously, the trees will have no more than 26 nodes. Here is a graphical representation of the tree used in the sample input and output: C / \ / \ B G / \ / A D H / \ E F The in-order traversal of this tree prints the left sub-tree, the root, and the right sub-tree. The pre-order traversal of this tree prints the root, the left sub-tree, and the right sub-tree. The post-order traversal of this tree print the left sub-tree, the right sub-tree, and the root. ---------------------------------------------------------------------------------------------------------------------------- 题目大意: 给出一棵二叉的中遍历 (inorder)前序遍历 (preorder)它的后序遍历 (postorder)。 输入描述 Line 1: The in-order representation of a tree. Line 2: The pre-o rder representation of that same tree. Only uppercase letter A-Z will appear in the input. You will get at least 1 and at most 26 nodes in the tree. 输出描述 A single line with the post-order representation of the tree. 样例输入 Copy to Clipboard ABEDFCHG CBADEFGH 样例输出 Copy to Clipboard AEFDBHGC c语言,代码不要有注释
最新发布
06-16
<think>我们已知二叉前序遍历和中遍历,可以唯一确定一棵二叉,并由此得到后序遍历。思路:1.前序遍历的第一个节点是根节点。2.在中遍历中找到根节点,则根节点左边是左子的中遍历,右边是右子的中遍历。3.根据左子的节点个数,可以在前序遍历中确定左子前序遍历和右子前序遍历。4.递归处理左子和右子。5.在递归构建二叉的过程中,我们按照后序遍历的顺(左子->右子->根节点)将节点值存入后序遍历数组。但是,题目要直接生成后序遍历数组,而不需要构建整个二叉的结构。我们可以通过递归函数直接填充后序遍历数组。具体步骤:假设我们有一个全局数组(或通过参数传递的数组)`postorder`来存储后序遍历结果,以及一个全局索引(或指针)`post_idx`表示当前要填充的位置(注意:由于后序遍历是左右根,所以应该从后往前填充,或者按顺填充再调整,但这里我们可以在递归中按顺填充,但是需要确定根节点的位置在最后)。然而,我们也可以这样:后序遍历的顺是左、右、根。所以当我们递归处理完左子和右子后,将根节点放入后序遍历数组的当前位置。但是,由于我们是从前序遍历得到根节点,然后递归左子和右子,最后才处理根节点,所以我们可以按照这个顺直接填充。但是注意:我们构建后序遍历数组时,需要知道整个的节点数,并且从数组的末尾开始填充(因为根节点在最后)?或者也可以按左子、右子、根的顺依次放入数组,但这样需要知道左子和右子的大小,然后确定根节点在数组中的位置。另一种方法:我们可以在递归过程中,每次将根节点放到当前子对应后序遍历区间的最后一个位置。因此,我们可以传递后序遍历数组的起始和结束位置,但这样比较复杂。这里我们采用一种更简单的方法:递归地构建左右子,并按照后序遍历的顺(先左子,再右子,最后根节点)将节点值存入数组。我们使用一个全局数组和一个全局索引(从0开始),在递归左子和右子后,将根节点存入数组,然后索引递增。这样得到的数组顺就是后序遍历。然而,这样得到的数组顺是:先左子的所有节点(按照后序),然后右子的所有节点(按照后序),最后根节点。这符合后序遍历的定义。但是,注意:前序遍历数组和中遍历数组我们如何传递?我们每次递归需要传递当前子前序遍历列和中遍历列(在原始数组中的起始位置和长度)。或者传递指针和长度。由于C语言中数组是连续内存,我们可以通过指针和长度来传递子对应的部分。设计递归函数:voidbuildPost(int*preorder,int*inorder,intn,int*postorder,int*postIndex)参数说明:preorder:当前子前序遍历数组的起始指针inorder:当前子的中遍历数组的起始指针n:当前子的节点个数postorder:后序遍历数组(全局)postIndex:当前后序遍历数组的填充位置(指针,因为递归中需要更新)递归过程:1.如果n==0,返回。2.前序遍历的第一个元素preorder[0]就是根节点。3.在中遍历数组inorder中找到根节点的位置,设为k(即inorder[k]==preorder[0])。那么,左子的节点个数为k,右子的节点个数为n-k-1。4.递归构建左子:buildPost(preorder+1,inorder,k,postorder,postIndex);5.递归构建右子:buildPost(preorder+1+k,inorder+k+1,n-k-1,postorder,postIndex);6.将根节点存入postorder[*postIndex],然后(*postIndex)++。注意:这样递归后,postorder数组中的顺就是后序遍历。但是,我们需要考虑如何在中遍历数组中找到根节点的位置。由于数组中可能有重复值?题目没有说明,通常假设节点值唯一。所以我们可以遍历inorder数组直到找到根节点。例子:前序遍历:[1,2,4,5,3,6,7]中遍历:[4,2,5,1,6,3,7]后序遍历:[4,5,2,6,7,3,1]递归过程:根节点1,在中遍历中找到1,位置为3(下标从0开始),则左子有3个节点(4,2,5),右子有3个节点(6,3,7)。左子前序遍历:从原前序遍历的第二个元素开始,取3个:[2,4,5]左子的中遍历:中遍历中1左边的3个元素:[4,2,5]递归左子:根节点2,在[4,2,5]中,2的位置是1,左子节点为1(4),右子节点为1(5)左子的左子前序遍历[4],中遍历[4]->递归后得到后序遍历:先处理左子(无,因为节点个数0),再处理右子(无),然后放入根节点4->所以后序遍历数组先放4。然后左子的右子前序遍历[5],中遍历[5]->放入5。然后左子的根节点2放入。所以左子部分的后序遍历是[4,5,2]。同理,右子部分:前序遍历[3,6,7],中遍历[6,3,7]->根节点3,在[6,3,7]中位置1,左子[6](1个节点),右子[7](1个节点)递归左子:放入6递归右子:放入7放入3所以右子部分的后序遍历是[6,7,3]最后放入根节点1。整个后序遍历:[4,5,2,6,7,3,1]代码实现中,需要注意在中遍历中查找根节点时,如果节点值唯一,遍历即可。但是,这个递归函数需要每次在inorder中线性查找根节点,时间复杂度O(n),整个算法时间复杂度O(n^2)。对于较大的,可以考虑先用哈希表存储中遍历的值和索引(但题目要C语言,且节点值如果是整数,且范围不大,可以用数组哈希,否则就线性查找)。由于题目没有说明节点值的范围,我们假设节点值为整数,且节点个数不会太大,所以采用线性查找。另外,递归函数中,我们每次传递的是当前子前序遍历数组的起始位置和中遍历数组的起始位置,以及节点个数。最后,我们还需要注意递归的终止条件:n==0。编写函数:输入:前序遍历数组pre,中遍历数组in,节点个数n输出:后序遍历数组post(需要提前分配好空间,大小与n相同)步骤:1.初始化一个索引变量postIndex=0(注意在递归中需要传递它的地址,以便递归中更新)。2.调用递归函数buildPost(pre,in,n,post,&postIndex);3.递归函数结束后,post数组中就是后序遍历结果。注意:这个递归函数中,我们按照左子、右子、根的顺将节点值放入post数组,并且postIndex从0开始递增,所以最后post数组就是后序遍历。但是,我们也可以不用全局索引,而是通过递归函数的返回值?这里我们传递索引的指针。代码实现如下:</think>### 二叉后序遍历生成代码实现(C语言) 根据二叉前序遍历和中遍历生成后序遍历的核心思路是通过递归重建二叉结构,然后进行后序遍历输出。以下是完整实现代码: ```c #include <stdio.h> #include <stdlib.h> // 二叉节点结构 typedef struct TreeNode { int val; struct TreeNode* left; struct TreeNode* right; } TreeNode; // 创建新节点 TreeNode* createNode(int value) { TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode)); newNode->val = value; newNode->left = NULL; newNode->right = NULL; return newNode; } // 在中数组中找到根节点位置 int findRootIndex(int inorder[], int start, int end, int rootValue) { for (int i = start; i <= end; i++) { if (inorder[i] == rootValue) { return i; } } return -1; // 未找到 } // 根据前序和中重建二叉 TreeNode* buildTree(int preorder[], int inorder[], int preStart, int preEnd, int inStart, int inEnd) { if (preStart > preEnd || inStart > inEnd) return NULL; // 前序遍历第一个元素是根节点 int rootValue = preorder[preStart]; TreeNode* root = createNode(rootValue); // 在中中找到根节点位置 int rootIndex = findRootIndex(inorder, inStart, inEnd, rootValue); int leftSubtreeSize = rootIndex - inStart; // 递归构建左子 root->left = buildTree( preorder, inorder, preStart + 1, preStart + leftSubtreeSize, inStart, rootIndex - 1 ); // 递归构建右子 root->right = buildTree( preorder, inorder, preStart + leftSubtreeSize + 1, preEnd, rootIndex + 1, inEnd ); return root; } // 后序遍历二叉并存储结果 void postorderTraversal(TreeNode* root, int result[], int* index) { if (!root) return; postorderTraversal(root->left, result, index); postorderTraversal(root->right, result, index); result[(*index)++] = root->val; } // 释放二叉内存 void freeTree(TreeNode* root) { if (!root) return; freeTree(root->left); freeTree(root->right); free(root); } // 主函数:生成后序遍历 void getPostorder(int preorder[], int inorder[], int n, int postorder[]) { TreeNode* root = buildTree(preorder, inorder, 0, n - 1, 0, n - 1); int index = 0; postorderTraversal(root, postorder, &index); freeTree(root); } // 测试用例 int main() { // 示例输入(前序和中遍历列) int preorder[] = {3, 9, 20, 15, 7}; int inorder[] = {9, 3, 15, 20, 7}; const int n = sizeof(preorder) / sizeof(preorder[0]); // 生成后序列 int postorder[n]; getPostorder(preorder, inorder, n, postorder); // 打印结果 printf("生成的后序遍历列: "); for (int i = 0; i < n; i++) { printf("%d ", postorder[i]); } printf("\n"); // 输出: 9 15 7 20 3 return 0; } ``` ### 关键步骤说明: 1. **重建二叉**(`buildTree`函数): - 前序列的第一个元素总是当前子的根节点 - 在中列中找到根节点位置,左侧为左子,右侧为右子 - 递归处理左子和右子[^3] 2. **后序遍历**(`postorderTraversal`函数): - 遍历:左子 → 右子 → 根节点 - 结果按顺存储到输出数组[^2] 3. **空间管理**: - 使用`freeTree`释放动态分配的节点内存 - 时间复杂度:$O(n^2)$(可优化为$O(n)$) - 空间复杂度:$O(n)$ ### 算法优化方向: 1. **查找优化**:使用哈希表存储中列的值-索引映射,将查找操作从$O(n)$降至$O(1)$ 2. **迭代实现**:改用栈替代递归,避免栈溢出风险 3. **直接生成**:不构建完整二叉,直接在递归过程中填充后序数组
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值