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
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/14574398.html