二叉树遍历非递归写法之大统一

  • Post author:
  • Post category:其他




在学习二叉树遍历时,大家都很容易接受递归写法,好理解。对于非递归写法,基本思想是用栈消除递归,但是教材上的前序、中序和后序基本上三个写法,还很难理解。博主亲身经历,找工作中,考查二叉树遍历的非递归写法还是常见的。所以决心整理出此文,方便理解和记忆二叉树遍历。

来复习一下二叉树的递归遍历。



  1. struct


    TreeNode {



  2. int


    data;


  3. TreeNode * left, * right;

  4. };



  5. void


    preOrderTraverse(TreeNode * root,


    void


    (*visit)(TreeNode *)) {



  6. if


    (root) {


  7. visit(root);

  8. preOrderTraverse(root->left, visit);

  9. preOrderTraverse(root->right, visit);

  10. }

  11. }



  12. void


    inOrderTraverse(TreeNode * root,


    void


    (*visit)(TreeNode *)) {



  13. if


    (root) {


  14. inOrderTraverse(root->left, visit);

  15. visit(root);

  16. inOrderTraverse(root->right, visit);

  17. }

  18. }



  19. void


    postOrderTraverse(TreeNode * root,


    void


    (*visit)(TreeNode *)) {



  20. if


    (root) {


  21. postOrderTraverse(root->left, visit);

  22. postOrderTraverse(root->right, visit);

  23. visit(root);

  24. }

  25. }

首先,博主找到了一篇类似论文,

《更简单的非递归遍历二叉树的方法》

,该文非常漂亮的将三种遍历方法统一起来,

除了有三行代码的顺序不一样外

。该实现中,对每个节点定义了一个pair对,通过改变局部入栈顺序实现整体遍历的不同。


本文意图使用常见的二叉树遍历的形式,简洁的实现这三种遍历。


void preOrderTraverseNonrecursive(TreeNode * root, void (*visit)(TreeNode *)) {


stack<TreeNode *> s;

TreeNode * p = root;

while(p != NULL || !s.empty()) {


while(p != NULL) {




visit(p);







s.push(p);



p = p->left;



}







if (!s.empty()) {






p = s.top();







p = p->right;







s.pop();



}



}



}


void inOrderTraverseNonrecursive(TreeNode * root, void (*visit)(TreeNode *)) {




stack<TreeNode *> s;



TreeNode * p = root;



while(p != NULL || !s.empty()) {




while(p != NULL) {







s.push(p);



p = p->left;



}







if (!s.empty()) {








p = s.top();







visit(p);







p = p->right;







s.pop();





}



}



}


void postOrderTraverseNonrecursive(TreeNode * root, void (*visit)(TreeNode *)) {




stack<TreeNode *> s;



TreeNode * p = root, *pre = NULL;



while(p != NULL || !s.empty()) {




while(p != NULL) {







s.push(p);



p = p->left;



}







if (!s.empty()) {








p = s.top();







if( p->right == NULL || p->right == pre) {








visit(p);




pre =

p;





p = NULL;




s.pop();







}







else {








p = p->right;







}



}



}



}

在前序遍历的代码中,最外层是while循环直到栈空且p为空,里面的while循环,不停访问根节点p,且迭代p=p->right,直到p为空。此时,从root开始的所有左分支的根节点均已被访问。到if分支时,取栈顶节点,切换到其右子树开始迭代。


前序遍历和中序遍历区别仅在于visit函数的调用位置不同



中序和后序的区别在于调用visit的时机不一致

。教材上的实现中,后序遍历需要一个标志来记录当前节点是否被访问过。在本文的实现中,通过一个pre指针记录被访问的最后一个元素,通过p->right==pre的比较来确认p是否被访问过。当然,直接访问p的另一情形是p没有右孩子。在这样的实现下,中序和后序的区别尽可能小。三种实现中,代码结构较一致,只有较少的变动。

希望本文可以方便那些学习非递归遍历二叉树的同学。

欢迎大家留言讨论!



版权声明:本文为dreamer2020原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。