节点遍历二叉树的遍历(递归、非递归)

这几周笔者几篇文章分析了改节点遍历的文章. 关联文章的地址

    景背

    二叉树是一种很基本的数据结构。很多地方能看到它的身影,比如赫赫有名的霍夫曼编码(好了,别问我再比如了,见地浅陋,真不知道更多了。。。)它的结构很洁简、奇妙。

    本文论讨二叉树的见常遍历方法的代码现实(这里贴出的是Java),包含前序(preorder)、序中(inorder)、后序(postorder)、层序(level order),进一步,斟酌递归和非递归的现实方法。递归法方的现实绝对简略,但递归的执行方法由于每次会都发生一个新的法方调用栈,如果递归层级较深,会消费较大的存内,转化为非递归则没那么简略了,常常须要现实一个栈来存保状态信息。

    在此之前,先简略义定节点的数据结构:

    二叉树节点最多只有两个儿子,并存保一个节点的值,为了试验的便利,定假它为int。同时,我们直接应用Java的System.out.print法方来输出节点值,以示显遍历结果。

public class Node {
        public int value;
        public Node leftNode;
        public Node rightNode;

        public Node(int i) {
            value = i;
        }
    }

    细详代码见参链接:BST及其各种便利的细详现实代码

    前序遍历

    

  • 递归现实:递归现实很简略,在每次问访到某个节点时,先输出节点值,然后再次依递归的对左儿子、右儿子调用遍历的法方。代码如下
public void preOrderTrav(Node n) {
        if (n != null) {
            System.out.print(n.value + " ");
            preOrderTrav(n.leftNode);
            preOrderTrav(n.rightNode);
        }
    }

    

  • 非递归调现实:

    1.第一种现实方法绝对易容解理:

    初始:护维一个栈,将根节点压入栈中。

    循环:每次从栈顶读出一个节点信息,直接将节点值输出,同时将儿子节点按从左到右的序顺推到栈顶。

    分析:跟递归调用的团体路思一样,不同的是,递归调用时是利用运行时统系所护维的程序调用栈来护维序顺,而这个非递归法方是用过自己护维的栈来存保信息。如此节省了调用栈的空间。

public void preOrderTravNoRecur(Node n) {
        Stack<Node> stack = new Stack<Node>();
        stack.add(root);
        while (!stack.empty()) {
            Node t = stack.pop();
            System.out.print(t.value + " ");
            if (t.rightNode != null)
                stack.add(t.rightNode);
            if (t.leftNode != null)
                stack.add(t.leftNode);
        }
    }

    2.第二种现实方法更遍普(序中遍历的非递归应用了一样的路思):

    初始:护维一个栈S和一个节点变量N。节点变量值赋为根节点。

    循环:将节点变量N的左儿子循环的输出,并推入栈S中,直到没有左儿子;推出栈S的顶节点,节点变量N值赋为栈S顶节点的右节点。

    分析:不同于递归调用的路思。栈S用于现实对某节点的边左支递归值的存储,以便溯回;节点变量N则用于遍历某节点的边右枝(这些节点是从栈S顶读出的节点,次依做处置),由于边右枝是最后才会被问访到的,故在处置边右枝的时候,不须要存储边右枝的信息,次依处置可即。

public void preOrderTravNoRecurII(Node n) {
        System.out.println("No Recursive: ");
        Stack<Node> s = new Stack<Node>();
        while (n != null | !s.empty()){
            while (n!=null ){
                System.out.print(n.value + " ");
                s.add(n);
                n = n.leftNode;
            }
            n = s.pop();
            n = n.rightNode;
        }
        System.out.println();
    }

    序中遍历

    

  • 递归现实
public void inorderTrav(Node n) {
        if (n != null) {
            inorderTrav(n.leftNode);
            System.out.print(n.value + " ");
            inorderTrav(n.rightNode);
        }
    }

    

  • 非递归现实

    初始:护维一个栈S和一个节点变量N。节点变量值赋为根节点。

    循环:将节点变量N的左儿子循环的输出,并推入栈S中,直到没有左儿子;节点变量N值赋为栈S顶节点的右节点。

    分析:跟前序遍历的非递归现实法方二很相似。独一的不同是输出的机会不同:前序遍历在入栈时输出,而序中遍历在出栈时输出。可以跟刻深的解理到,栈在这里是为了溯回而存在的。

 

    每日一道理
在个每人心中,都曾停留过那些值得怀念的人,也许还在,也许早已消逝,在茫茫人海中丢失,于是,那份怀念便得凄凉,因为模糊的记忆中只剩下一个“空壳”,没有什么,甚至连自己的心都装不下,间时把一切抹平,也把当日的泪水封锁,因为已没有,怀念只是悲凉!
public void inorderTravNoRecu(Node n) {
        System.out.println("No Recursive: ");
        Stack<Node> s = new Stack<Node>();
        while (n != null | !s.empty()){
            while (n!=null ){
                s.add(n);
                n = n.leftNode;
            }
            n = s.pop();
            System.out.print(n.value + " ");
            n = n.rightNode;
        }
    }

    后序遍历

    

  • 递归现实
public void preOrderTravNoRecurII(Node n) {
        System.out.println("No Recursive: ");
        Stack<Node> s = new Stack<Node>();
        while (n != null | !s.empty()){
            while (n!=null ){
                System.out.print(n.value + " ");
                s.add(n);
                n = n.leftNode;
            }
            n = s.pop();

            n = n.rightNode;
        }
        System.out.println();
    }

    

  • 非递归现实

    初始:1.护维一个栈S、一个节点变量N和一个标记数组。节点变量值赋为根节点,栈临时存储便利到的节点,标记数组用于标记栈中的节点否是已问访过边右节点。2.将根节点的有所左儿子压入栈中。

    循环:次依处置栈中节点。如果节点有右儿子,且没有被处置过(通过标记数组判断),则将右子树的根节点及其左儿子部全压入栈中;如果已处置过或者没有右儿子,则输出并出栈。

    分析:与前序和序中的一个大的不同在于须要用标记数组标记节点的右子树否是已问访过。对个每节点停止处置的时候,都保障已处置完了右左子树(通过先压入边左儿子为主线,处置栈中的个每节点时,再压入边右儿子来现实)。

public void postOrderTravNoRecu(Node n) {       
        Stack<Node> stack = new Stack<Node>();
        int[] flag = new int[max];

        while (n != null) {
            stack.push(n);
            flag[stack.size()] = 0;
            n = n.leftNode;
        }

        while (!stack.empty()) {
            n = stack.peek();
            while(n.rightNode != null && flag[stack.size()] == 0) {
                n = n.rightNode;
                flag[stack.size()] = 1;
                while (n != null) {
                    stack.push(n);
                    flag[stack.size()] = 0;
                    n = n.leftNode;
                }
                n = stack.peek();//TODO be careful about this
            }
            n = stack.pop();
            System.out.print(n.value + " ");
        }

    }

    层序遍历

    

  • 没法应用递归法方

    层序遍历不同于其他的遍历。可以通过反证法证明:

    如果能现实对A节点的层序递归,在对A节点处置的中程过,该应递归的对两个儿子B和C别分调用了层序遍历。在种这情况下,我们没法让B和C的同一个层级的儿子在中集的间时中被遍历到,换言之,B的第一层儿子在对B的调用中被遍历,而C的第一层儿子,则在对C的调用中遍历,这是分分开的。不成立,得证。

    

  • 非递归法方:

    分析:此法方相似于前序遍历的非递归法方的第一种。用一个栈护维信息。

    

public void levelOrderTrav(Node n) {
        System.out.print("Level OrderTrav: ");

        Queue<Node> q = new LinkedList<Node>();
        q.add(n);
        while (q.size() != 0) {
            n = q.poll();
            System.out.print(" " + n.value);
            if (n.leftNode != null) 
                q.add(n.leftNode);
            if (n.rightNode != null)
                q.add(n.rightNode);

        }
    }

    总结

    非递归现实的代码绝对来说没有递归现实的直观。其心核都是护维了一个栈来存保状态,避免了发生多过法方调用栈糟蹋存内空间。

    本文中针对二叉树的几种遍历方法,描述了递归和非递归的解决方案。遍普意义的递归转非递归的法方和想思,将在另外一篇博文中分析;)。迎欢交流。

    

    PS:迎欢问访新博客,这里有更好的排版~点击开打链接

文章结束给大家分享下程序员的一些笑话语录: 问:你觉得让你女朋友(或者任何一个女的)从你和李彦宏之间选一个,你觉得她会选谁?  
  答:因为李艳红这种败类,所以我没女友!

posted @ 2013-05-05 22:03  坚固66  阅读(349)  评论(0编辑  收藏  举报