在数据结构中,二叉树遍历是一个基础但极其重要的概念。传统的中序遍历递归实现虽然简洁,但理解其非递归版本对于掌握栈的应用和遍历本质更为关键。通过栈操作序列重建二叉树,再输出其后序遍历结果,这个题目完美结合了理论知识和实际应用。
中序遍历的非递归实现依赖于栈这种数据结构来模拟递归调用的过程。基本思路是:
这种方法的精妙之处在于,它通过显式的栈操作记录了遍历路径,而题目给出的Push/Pop序列正是这种遍历过程的完整记录。例如,Push 1→Push 2→Push 3→Pop→Pop表示先沿着左子树深入,然后回溯的过程。
题目给出的栈操作序列具有两个关键特征:
这种对应关系不是巧合,而是由中序遍历的栈实现方式决定的。理解这一点是解决本题的关键——我们可以利用Push序列重建树的前序遍历,Pop序列则直接给出了中序遍历结果。
首先需要定义二叉树的基本结构:
cpp复制struct Node {
int val;
Node *l;
Node *r;
};
在读取输入时,我们需要维护两个关键数组:
qian:记录Push顺序,即前序遍历序列zhong:记录Pop顺序,即中序遍历序列cpp复制vector<int> zhong, qian;
stack<int> st;
for(int i=0;i<2*n;++i){
string s;
getline(cin,s);
if(s.find("Push")!=string::npos){
int k=stoi(s.substr(5));
qian.push_back(k);
st.push(k);
}else{
zhong.push_back(st.top());
st.pop();
}
}
有了前序和中序遍历序列,就可以采用分治策略递归构建二叉树:
cpp复制Node* build(int ql,int qr,int zl,int zr,vector<int> qian,vector<int> zhong){
if(ql>qr) return nullptr;
int rootVal = qian[ql];
Node* root = create(rootVal);
int k = zl;
while(k <= zr && zhong[k] != rootVal) k++;
int leftSize = k - zl;
root->l = build(ql+1, ql+leftSize, zl, k-1, qian, zhong);
root->r = build(ql+leftSize+1, qr, k+1, zr, qian, zhong);
return root;
}
这个递归函数的核心思想是:
后序遍历的顺序是"左-右-根",递归实现最为直观:
cpp复制vector<int> ans;
void postOrder(Node* root) {
if(!root) return;
postOrder(root->l);
postOrder(root->r);
ans.push_back(root->val);
}
题目要求输出结果数字间用单个空格分隔,行末不得有多余空格。可以采用以下输出策略:
cpp复制for(int i=0;i<ans.size();i++){
if(i>0) cout<<" ";
cout<<ans[i];
}
这种输出方式避免了在最后一个数字后输出多余空格的问题。
主函数负责协调整个流程:
cpp复制int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n; cin>>n; cin.ignore();
// 读取栈操作并构建前序、中序序列
// ...
Node* root = build(0, n-1, 0, n-1, qian, zhong);
postOrder(root);
// 输出结果
// ...
return 0;
}
虽然题目中N≤30,不需要考虑性能问题,但在实际应用中可以考虑以下优化:
在实际编码中,有几个边界条件需要特别注意:
当程序出现问题时,可以:
例如,对于样例输入,可以添加调试输出:
cpp复制cout << "前序: ";
for(int x : qian) cout << x << " ";
cout << "\n中序: ";
for(int x : zhong) cout << x << " ";
虽然本题规模小,但良好的习惯是在程序结束前释放动态分配的内存:
cpp复制void deleteTree(Node* root) {
if(!root) return;
deleteTree(root->l);
deleteTree(root->r);
delete root;
}
// 在main函数结束前调用
deleteTree(root);
类似的思路可以应用于:
每种组合都有其特点和限制,例如仅凭前序和后序无法唯一确定一棵二叉树。
这种技术在以下场景中有实际应用:
掌握这个基础后,可以尝试解决以下进阶问题:
在实际编码比赛中,这类问题常常以各种变体形式出现。理解其核心原理后,就能灵活应对各种变化。