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
根据一个栈的出栈入栈顺序可以确定一个二叉树。
先想一下我们如何用栈来遍历二叉树:如果是先序遍历的话,碰到一个结点无脑先压入栈,等到左右子树都遍历完之后最后弹出这个结点。那么题目中的栈的压入顺序就是这棵二叉树的先序遍历的顺序。
那么出栈顺序又是什么呢?
把某个结点压入栈是对该结点的第一次访问,弹出这个结点是对这个结点的第二次访问,那么出栈的顺序就是这棵二叉树的中序遍历。
既然有了先序和中序遍历,便可以得到一棵唯一的二叉树。这样求后序遍历就很简单了。
第一个是笨方法,就是根据得到的先序后序遍历还原这棵二叉树,然后后序遍历这二叉树。但是比较麻烦。
第二个是一般方法,是根据先序后序遍历的两个序列直接求后序遍历的序列:
#include <iostream>
#include <stack> //c++的STL
#define MAXSIZE 30
using namespace std;
int pre[MAXSIZE],in[MAXSIZE],post[MAXSIZE];
//分别代表先序 中序 后序的遍历序列
//下面这个递归的总体意思就是 每次都把当前三个序列中的先序序列第一个节点放到后序序列的最后一个节点
//然后从中序序列里找到该节点的位置 并统计 该节点之前的所有数 和 该节点之后的所有数
//然后依次求上面统计的这两个序列
void requestPostOrder(int preN, int inN, int postN, int n){
// preN(当前先序序列的首位置),inN(当前中序序列的首位置), postN(当前后序序列的首位置)
// n 当前序列的总个数
//考虑递归到最后的情况
if (n==0) return;//如果没有左右子树 返回
if (n==1) {//如果左右子树只有一个节点了
post[postN] = pre[preN];
return;
}
//当前序列中的 先序序列的第一个元素(节点)复制到后续遍历的最后一个结点
//然后每次递归都是这样执行
int last = pre[preN]; //得到当前先序序列的第一个结点
post[postN+n-1] = last;//复制到后续遍历的最后一个
//从中序遍历中找到当前节点的位置(以区分左右总结点的个数)
int i;
for (i=0; i<n; i++) {
if (in[inN+i] == last) break; //易错点
}
int left = i;//当前根结点左边序列的总数
int right = n-left-1;//-1是减去了当前树的根节点 右边序列的总数
//递归求左子树
//preN+1 是因为preN已经被放到后序数组的最后位置 现在需要知道下一个根节点
requestPostOrder(preN+1, inN, postN, left);
//递归求右子树
requestPostOrder(preN+left+1, inN+left+1, postN+left, right);
}
int main(){
int N;
cin>>N;//结点数
int num;
int a=0;
int b=0;
stack<int>ST;//c++ STL
char s[10];
//读取指令 并保存完成先序 中序遍历的数组
for (int i=0; i<2*N; i++) {
cin>>s;
if (s[1] == 'u') { //push
cin>>num;
pre[a++]=num;
ST.push(num);
}
else { //pop
num = ST.top();
in[b++]=num;
ST.pop();
}
}
requestPostOrder(0, 0, 0, N);
//格式输出
cout<<post[0];
for (int i=1; i<N; i++) {
cout<<" "<<post[i];
}
cout<<endl;
return 0;
}
第三个是很巧妙的写法:
#include <stdio.h>
struct Node{
int tag; //标记节点是第几次进栈
int num;
};
//先序遍历对应进栈顺序,中序遍历对应出栈顺序;
//后序遍历与中序遍历不同的是节点出栈后要马上再入栈(tag做第二次入栈标记),等右儿子遍历完后再出栈;
//具体实现上,每次中序遍历的pop时,如果栈顶是标记过的节点(tag=2),循环弹出;如果没有标记过(tag=1),做标记,即弹出再压栈)
//栈顶tag=2的节点对应中序遍历中已弹出的节点;循环弹出后碰到的第一个tag=1的节点才对应中序遍历当前pop的节点
int main() {
int n;
scanf("%d", &n);
int flag = 0;
struct Node stack[30];
int size = 0; //栈元素大小,指向栈顶的下一个位置
for (int i = 0; i < 2 * n; ++i) {
char s[10];
scanf("%s", s);
if (s[1] == 'u') { //push
scanf("%d", &stack[size].num); //入栈
stack[size].tag = 1; //标记第一次入栈
++size;
}
else { //pop
while (size > 0 && stack[size - 1].tag == 2) { //循环弹出栈顶tag=2的节点
if (flag)
printf(" ");
flag = 1;
printf("%d", stack[--size].num);
}
if (size > 0) //将中序遍历中应该要弹出的节点弹出再压栈,做标记即可
stack[size - 1].tag = 2;
}
}
while (size) { //将栈中剩余节点依次弹出
if (flag) {
printf(" ");
}
flag = 1;
printf("%d", stack[--size].num);
}
return 0;
}