Tree Traversals Again

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

 

解题思路

  题目通过模拟一个栈来对元素进行Push和Pop来实现遍历。所以我们可以根据这个栈对各个元素的操作,来建立一颗二叉树。然后再递归后序遍历二叉树,就完成题目了。

  按照种思路的话,关键是要分清楚压进来的元素,哪些应该插在左子树,哪些应该插在右子树。

  我们的插入规则是这样的,当有一个新元素准备要压入栈时(注意,此时新元素还没有压入栈),这时我们先要对上一次的操作进行判断。

  如果上一次的操作是Push,那么我们就把新元素插在栈顶元素的左子树,然后再把新元素压入栈中。

  如果上一次的操作是Pop,那么我们就把新元素插在最后一次弹出,也就是上一次弹出元素的右子树(注意,不是插在栈顶元素的右子树),然后再把新元素压入栈中。

  其实我们可以从测试样例中找到这样的规律。知道了插入的规则后,代码就很容易实现了。

  下面给出AC代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <stack>
 4 
 5 struct TNode {
 6     int data;
 7     TNode *left, *right;
 8 };
 9 
10 void postOrderTraversal(TNode *T);
11 
12 int main() {
13     int n;
14     scanf("%d", &n);
15     
16     TNode *T = NULL;                        // T指向我们生成的二叉树的根节点 
17     TNode *pre = NULL;                      // pre用来指向刚压入栈中的节点,或是最后一次弹出的节点 
18     std::stack<TNode*> S;                   // 申请一个栈 
19     bool isPush = true;                     // 用来标识上一次的操作是Push,还是Pop 
20     char op[5]; 
21     
22     for (int i = 0; i < 2 * n; i++) {
23         scanf("%s", op);
24         if (strcmp(op, "Push") == 0) {      // 如果是Push操作 
25             TNode *p = new TNode;           // 为压入的元素生成一个节点 
26             scanf("%d", &p -> data); 
27             p -> left = p -> right = NULL;
28             
29             if (T == NULL) T = p;           // 如果该节点是第一个节点,那么它就是整一颗二叉树的根节点,用T来存放它的地址 
30             
31             if (pre) {                      // 按照插入规则,新节点应该插在刚压入栈中的节点,或是最后一次弹出的节点,同时要求这个节点存在 
32                 if (isPush) pre -> left = p;// 如果上一次操作是Push,那么新节点就插入到在pre的左子树 
33                 else pre -> right = p;      // 如果上一次操作是Pop,那么新节点就插入到在pre的右子树 
34             }
35             
36             pre = p;                        // 因为现在是Push操作,更新pre,指向新节点 
37             S.push(p);                      // 把新节点压入栈中 
38             isPush = true;                  // 上一次的操作变成了Push 
39         }
40         else {                              // 如果是Pop操作 
41             pre = S.top();                  // 因为现在是Pop操作,更新pre,指向要弹出的节点 
42             S.pop();                        // 把栈顶元素弹出 
43             isPush = false;                 // 上一次的操作变成了Pop
44         }
45     }
46     
47     postOrderTraversal(T);                  // 到这里二叉树就建立好了,我们只需要后序遍历它 
48     
49     return 0;
50 }
51 
52 void postOrderTraversal(TNode *T) {
53     static int num = 0;                     // 用来控制空格的输出 
54     if (T) {
55         postOrderTraversal(T -> left);
56         postOrderTraversal(T -> right);
57         if (num++) putchar(' ');            // 第一个元素前面不应该输出空格 
58         printf("%d", T -> data);
59     }
60 }

 

  上面的方法是我第一次做时想出来的。

  下面的方法是参考姥姥的思路写出来的,这个方法很巧妙,当时完全没想到。

  其实,题目是用栈来模拟,树的非递归遍历过程。通过这个栈,每个节点会被访问两次。当Push时就对应着先序遍历,当Pop时就对应中序遍历。

  当Push时,就把元素压入栈,同时也把元素存放到用来记录先序遍历结果的pre数组中。

  当Pop时,就把栈顶元素弹出,同时把弹出的元素存放到用来记录中序遍历结果的in数组中。

  所以当我们输入完成后,就可以得到先序遍历结果的数组和后序遍历结果的数组。

  有了先序序列和中序序列后,就可以确定一颗二叉树。然后我们再通过这个先序序列和中序序列,来得到后序遍历结果的序列,这就不需要建立一颗树了。

  如何得到后序序列呢?就是把先序序列的第一个元素,也就是根节点,存放到后序序列的最后一个位置,再从in数组中找到根节点的下标位置,把in数组划分为左子树和右子树两个部分。把左子树和右子树对应的后序遍历的结果,分别存放到后序序列与之对应的部分。

  而左子树和右子树也是用上述的方法得到相对应的后序序列,所以整一个求后序序列的过程可以用递归来实现。

  我们的递归函数就做这件事情:传入三个数组最左边第一个位置的下标,以及需要操作的节点数量,把pre数组的第一个位置的元素存放到post数组最后一个位置,然后在in数组中找到pre数组中第一个元素的下标,根据这个下标找到左子树和右子树的元素,用递归的方法把左子树和右子树放到post数组对应的位置,就得到了后序遍历的序列了。

  AC代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <stack>
 4 
 5 int pre[30], in[30], post[30];
 6 
 7 void postOrderTraversal(int preL, int inL, int postL, int n);
 8 
 9 int main() {
10     int n;
11     scanf("%d", &n);
12 
13     int num1 = 0, num2 = 0;
14     std::stack<int> S;
15     char op[5];
16 
17     for (int i = 0; i < 2 * n; i++) {
18         scanf("%s", op);
19         if (strcmp(op, "Push") == 0) {
20             scanf("%d", &pre[num1]);    // Push操作时,对应先序遍历,把元素存放到pre数组中
21             S.push(pre[num1]);
22             num1++;
23         }
24         else {
25             in[num2++] = S.top();       // Pop操作时,对应中序遍历,把栈顶元素弹出,存放到in数组中
26             S.pop();
27         }
28     }
29 
30     postOrderTraversal(0, 0, 0, n);
31 
32     for (int i = 0; i < n; i++) {
33         if (i) putchar(' ');
34         printf("%d", post[i]);
35     }
36 
37     return 0;
38 }
39 
40 void postOrderTraversal(int preL, int inL, int postL, int n) {
41     if (n) {                                                // 递归的终止条件是节点数为零,也就是 n == 0 
42         post[postL + n - 1] = pre[preL];                    // 把pre的第一个元素,也就是根节点,存放到post的最后一个位置
43 
44         int pos = inL;
45         while (pos < inL + n && in[pos] != pre[preL]) {     // 通过循环,找到根节点在in中对应的下标位置
46             pos++;
47         }
48 
49         int leftNum = pos - inL;                            // 左子树的节点个数
50         int rightNum = n - 1 - leftNum;                     // 右子树的节点个数
51 
52         postOrderTraversal(preL + 1, inL, postL, leftNum);  // 通过递归得到左子树的后序序列并存放到post前leftNum个位置
53         postOrderTraversal(preL + leftNum + 1, pos + 1, postL + leftNum, rightNum); // 通过递归得到右子树的后序序列并存放到post剩下的righttNum个位置
54     }
55 }

 

参考资料

  浙江大学——数据结构:https://www.icourse163.org/course/ZJU-93001?tid=1461682474

posted @ 2021-03-25 19:00  onlyblues  阅读(144)  评论(0编辑  收藏  举报
Web Analytics