参考了一些别人的博客和评论,整理出来的:http://blog.youkuaiyun.com/fansongy/article/details/6798278/
值得强调的一点是,在出栈操作后,对弹出指针指向的结点(一般是父结点),尽量采用直接操作的方式,关键在于要避免下次循环进来时,指针仍指向该弹出的结点,容易造成重复访问。
树的结点结构如下:
struct TreeNode{
int val;
TreeNode* leftkid;
TreeNode* rightkid;
TreeNode() :val(0), leftkid(NULL), rightkid(NULL) {};
};
前序遍历:
void PreOrderTraverse(TreeNode* root) {
stack<TreeNode*> Tstack;
TreeNode* p = root;
while (p || !Tstack.empty()) {
while (p) {
cout << p->val << " ";
Tstack.push(p);
p = p->leftkid;
}
p = Tstack.top()->rightkid;
Tstack.pop();
}
return;
}
中序遍历:
void MidOrderTraverse(TreeNode* root) {
stack<TreeNode*> Tstack;
TreeNode* p = root;
while (p || !Tstack.empty()) {
while (p) {
Tstack.push(p);
p = p->leftkid;
}
cout << Tstack.top()->val << " ";
p = Tstack.top()->rightkid;
Tstack.pop();
}
return;
}
后续遍历:
void PostOrderTraverse(TreeNode* root) {
stack<TreeNode*> Tstack;
TreeNode* p = root;
while (true) {
while (p) {
Tstack.push(p);
p = p->leftkid;
}
while (!Tstack.empty() && p == Tstack.top()->rightkid) {
p = Tstack.top();
cout << p->val << " ";
Tstack.pop();
}
if (Tstack.empty()) break;
p = Tstack.top()->rightkid;
}
return;
}
对出栈操作的部分,由于出栈的结点都是之前访问过的,所以不需要(应该说要避免)再次去访问它的子节点。那么对它来说只有两种需要考虑的情况:作为其父节点的左子节点或者右子节点。如果是左子节点,那么接下来要访问其兄弟节点;如果是右子节点,则说明其父节点的所有子节点已访问完毕,这时应访问父节点。
后序遍历还可以这样实现:
把先序遍历的代码中的left全换为right,right全换为left,最后就得到了后序遍历的逆序,反向输出就是后序遍历的顺序了。
【但是不建议用这种方式,很多面试笔试目的在于考察对树结构的理解以及栈的操作。此外,某些情况下,结点内可能保存着重要的拓扑结构,改变访问顺序对结点影响很大】