03-树3 Tree Traversals Again

 

题目

An inorder binary tree traversal can be implemented in a non-recursive way with a stack. For example, suppose that when a 6-node binary tree (with the keys numbered from 1 to 6) is traversed, the stack operations are: push(1); push(2); push(3); pop(); pop(); push(4); pop(); pop(); push(5); push(6); pop(); pop(). Then a unique binary tree (shown in Figure 1) can be generated from this sequence of operations. Your task is to give the postorder traversal sequence of this tree.

                                                                      
                                                                                  Figure 1

Input Specification:

Each input file contains one test case. For each case, the first line contains a positive integer N (≤30) which is the total number of nodes in a tree (and hence the nodes are numbered from 1 to N). Then 2N lines follow, each describes a stack operation in the format: "Push X" where X is the index of the node being pushed onto the stack; or "Pop" meaning to pop one node from the stack.

Output Specification:

For each test case, print the postorder traversal sequence of the corresponding tree in one line. A solution is guaranteed to exist. All the numbers must be separated by exactly one space, and there must be no extra space at the end of the line.

Sample Input:

6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop

Sample Output:

3 4 2 6 5 1

要求

时间限制: 400 ms
内存限制: 64 MB
代码长度限制: 16 KB

题意理解

这道题涉及到了树的先序、中序和后序遍历,且不需要建一棵树。

在树的递归中序遍历中,从根结点出发,先递归地访问左子树,然后打印根结点,再递归地访问右子树。

而在非递归中序遍历中,自己写一个堆栈,模拟系统堆栈的行为。

         

针对这棵树,先将1压入堆栈,然后访问左边,将当前子树的根结点(2)压入堆栈,继续访问左边,把3压入堆栈。再继续访问左边,发现左边为空,将3弹出(即打印);返回,打印2,然后访问2的右边。以此类推完成非递归的中序遍历。可以得到如下规律:

  • 非递归中序遍历
    • Push的顺序为先序遍历:看到根节点,先压入堆栈
    • Pop的顺序给出中序遍历

样例输入中:

需要两个数组pre(先序遍历数组)和in(中序遍历数组),还需要开一个数组作为堆栈。

pre:1 2 3 4 5 6 

  in: 3 2 4 1 6 5

可以建立一棵树,然后通过后序遍历得到结果;但是还有一种方法是不建立树,直接得到后序遍历的结果。

核心算法

建立新的数组post(存放后序遍历的结果)

根据pre和in的数字分布情况,直接得到post结果。

pre数组存的第一个元素必定是根结点,而在后序遍历中根结点是最后一个被访问到的,所以pre的第一个元素一定会被放到post的最后一个元素的位置上。然后分而治之,分别递归的处理左边和右边的问题。

                                           

如何知道左边和右边各包含哪些元素呢?要知道这个,就要依赖中序遍历的结果。所以通过in数组中根结点的位置,就自然地将问题分成了两块,根结点左边的元素都是属于左子树的,根结点右边的元素都是属于右子树的。

                                         

结点的存储顺序在先序遍历里多半是不一样的,但是元素的个数是知道的。知道从根结点往后数3个一定是属于左子树的,后面连个一定是属于右子树的。

                                        

以此类推,在post数组中前三个元素不知道是什么,但是一定存的是左子树的,后面两个元素一定存的是右子树的。这时,就可以递归的解决左边的问题,然后递归的解决蓝色的。

                                        

  • 伪代码
/** 
 *
 * preL:先序遍历最左边元素的位置,即在数组中的下标 
 * inL:中序遍历第一个结点的位置,即在数组中的下标 
 * postL:后序遍历第一个结点的位置,即在数组中的下标 
 * n:当前数组中元素的个数
 */     

void solve(int preL, int inL, int postL, int n)
{
    if (n == 0) 
        return;
    if (n == 1) {
        post[postL] = pre[preL];
        return;
    }
    root = pre[preL]; //确定根结点
    post[postL + n - 1] = root; //将root写入到post的最后一个结点的位置
    for(i = 0; i < n; i++) 
        if (in[inL + i] == root)  //从左到右扫描in数组,找根结点的位置。与此同时,知道了左边和右边分别有多少元素
            break;
    L = i;  //左边的元素个数
    R = n-L-1; //右边的元素个数
    //接下来,递归地去解决左边和右边的问题
    solve(preL+1, inL, postL, L); //pre数组现在要处理第2个元素,所以当前最左边元素的下标是preL + 1;而in和post两个数组最左边的位置不变
    solve(preL+L+1, inL+L+1, postL+L, R);
}

上述代码可以依照样例走一遍。

一开始,找到根结点为1,就将1写到post的最后位置;然后递归的处理左边,即处理pre中的2,将其写到如下post中的位置:

再找2在in中的位置,正好在中间,左边有一个3,右边有一个4,递归地调用左边,此时左边只有1个元素,且此时的preL也是元素3的下标,所以将3放到post中的得一个位置:

递归地调用右边,所以4放到post中第2个位置:

至此,左边的递归调用完成了。接下来是递归地处理右边。此时的preL对应的元素是5,所以将5写到post的倒数第二个位置:

而通过5在in数组中的位置,可以知道5左边还有6,所以6被写到了post中5前面一个位置:

而5右边是没有元素了,应该结束了。但是程序是不知道的,会继续递归地调用右边,此时对应的就是n = 0的情形,什么都不做,直接返回,程序才能正常结束。

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERROR -2
#define MAXSIZE 30
typedef int ElementType;
typedef struct SNode *Stack;
struct SNode
{
    ElementType *Data;
    int Top;
    int Capacity;
};

ElementType pre[MAXSIZE];
ElementType in[MAXSIZE];
ElementType post[MAXSIZE];

Stack CreateStack(int MaxSize)
{
    Stack S = (Stack)malloc(sizeof(struct SNode));
    S->Data = (ElementType *)malloc(sizeof(ElementType) * MaxSize);
    S->Top = -1;
    S->Capacity = MaxSize;
    return S;
}

void Push(Stack S, ElementType item)
{
    if (S->Top == S->Capacity)
        return;
    S->Data[++(S->Top)] = item;
}

ElementType Pop(Stack S)
{
    if (S->Top == -1)
        return ERROR;
    return S->Data[(S->Top)--];
}

/**
 *
 * preL:先序遍历最左边元素的位置,即在数组中的下标
 * inL:中序遍历第一个结点的位置,即在数组中的下标
 * postL:后序遍历第一个结点的位置,即在数组中的下标
 * n:当前数组中元素的个数
 */

void solve(int preL, int inL, int postL, int n)
{
    int L;
    int R;
    int root;
    if (n == 0)
        return;
    if (n == 1) {
        post[postL] = pre[preL];
        return;
    }
    root = pre[preL]; //确定根结点
    post[postL + n - 1] = root; //将root写入到post的最后一个结点的位置
    int i;
    for(i = 0; i < n; i++)
        if (in[inL + i] == root)  //从左到右扫描in数组,找根结点的位置。与此同时,知道了左边和右边分别有多少元素
            break;
    L = i;  //左边的元素个数
    R = n-L-1; //右边的元素个数
    //递归地去解决左边和右边的问题
    solve(preL+1, inL, postL, L); //pre数组现在要处理第2个元素,所以当前最左边元素的下标是preL + 1;而in和post两个数组最左边的位置不变
    solve(preL+L+1, inL+L+1, postL+L, R);
}

int main()
{
    Stack S = CreateStack(MAXSIZE);
    int N;
    scanf("%d", &N);
    int i;
    int preIndex = 0;
    int inIndex = 0;
    char str[5];
    for(i = 0; i < 2*N; i++)
    {
        int num;
        scanf("%s", str);
        if(strcmp(str, "Push") == 0) {//是Push
            scanf("%d", &num);
            pre[preIndex++] = num;
            Push(S, num);
        } else  {//是Pop
            in[inIndex++] = Pop(S);
        }
    }
    /*printf("pre:");
    for (i = 0; i < N; i++) {
        printf("%d ", pre[i]);
    }
    printf("\n");
    printf("in:");
    for (i = 0; i < N; i++) {
        printf("%d ", in[i]);
    } */
    solve(0,0,0, N);
    //printf("\n");
    //printf("post:");
    for (i = 0; i < N - 1; i++) {
        printf("%d ", post[i]);
    }
    printf("%d\n", post[N-1]);
    return 0;
}

运行结果

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值