zoukankan      html  css  js  c++  java
  • 树的前序后序中序遍历

    在计算机科学里,树的遍历是指通过一种方法按照一定的顺序访问一颗树的过程。

    对于二叉树,树的遍历通常有四种:先序遍历、中序遍历、后序遍历广度优先遍历。(前三种亦统称深度优先遍历)对于多叉树,树的遍历通常有两种:深度优先遍历、广度优先遍历。

    在学习前面三种深度优先遍历之前,很有必要了解它们之间到底是怎么遍历的,要自己去亲自去遍历,不要只看文字

    先序遍历:  节点 - 左孩子 - 右孩子

    中序遍历: 左孩子  - 根结点 - 右孩子

    后序遍历 : 左孩子 - 右孩子 - 根结点

    看下面的图(自己去写出几种遍历的情况):

    D8FFU9XE`N9BXA9@NW}Z{K0 

    按照上面的三种方法去遍历,结果是

    前序遍历:- + a * b – c d / e f
    中序遍历:a + b * c – d – e / f
    后序遍历:a b c d – * + e f / -

    广度优先遍历,又叫层次遍历:从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。

    在这幅图里所的结果是:- + / a * c f b – c d

    下面给出java实现上面几种遍历的算法以及解释:

    三种递归的算法不说了,直接说非递归算法:

    前序非递归实现 

    节点 - 左孩子 - 右孩子
         1)访问结点P,并将结点P入栈;
         2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
         3)直到P为NULL并且栈为空,则遍历结束。

    有两种实现方法:

    public static void preOrder1_1(BinTree t){
            Stack<BinTree> s = new Stack<BinTree>();
            while(t != null || !s.empty()){       //只要满足节点非空或则栈为非空,就要不断遍历
                
                while(t !=null ){                 //压入所有左孩子节点,压入前输出
                    System.out.print(t.date);
                    s.push(t);
                    t = t.lchild;
                }
                if(!s.empty()){        //左孩子都搞定了,从栈顶取出一个节点,指向它的右孩子,继续前面while循环
                    t = s.pop();
                    t = t.rchild;
                }
            }
            System.out.println();
        }
        public static void preOrder1_2(BinTree t){
            Stack<BinTree> stack = new Stack<BinTree>();
            if(t!=null)
            {
                stack.push(t);
                while(!stack.empty())
                {
                    t = stack.pop();
                    System.out.print(t.date);
                    if(t.rchild!=null)         //先把右孩子压入栈,利用下压栈的特性
                        stack.push(t.rchild);
                    if(t.lchild!=null)
                        stack.push(t.lchild);                
                }
            }
        }
    View Code

    中序非递归算法:

      左孩子  - 根结点 - 右孩子
         *访问任意一节点p,若其左孩子非空,p入栈,且p的左孩子作为当前节点,然后在对其进行同样的处理
         *若其左孩子为空,则输出栈顶点元素并进行出栈操作,访问该栈顶的节点的右孩子
         *直到p为null并且栈为空则遍历结束
       (和前序的非递归算法1_1有相似之处)

    public static void InOrder2_1(BinTree t){
            Stack<BinTree> s = new Stack<BinTree>();
            while(t != null || !s.empty()){
                while(t !=null){           //和前序的非递归有点像
                    s.push(t);
                    t = t.lchild;           //一路指向左孩子
                }
                if(! s.empty()){
                    t = s.pop();              //取栈顶元素并输出,并指向右孩子
                    System.out.print(t.date);
                    t = t.rchild;
                }
            }
            System.out.println();
        }
    View Code

    后序非递归算法

    左孩子 - 右孩子 - 根结点

    这个有难度  因为要保证左孩子和右孩子都已经被访问过了才访问根节点,这里就要思考一下如何标记一下右孩子是否已经被标记了。有两种方法:

    双栈法(这个比较容易理解)
         * 对于跟节点t,先入栈,然后沿着其左子树往下收索,直到没有左子树的节点,此时该节点入栈
         * 但此时不能进行出栈访问,要检查右孩子,所以接下来一相同的规则进行处理,当访问其右孩子时,
         * 该节点又出现在栈顶,此时就可以出栈访问,这样就保证了正确的访问顺序,这个过程中,
         * 每个节点都两次出现在栈顶,只有在第二次出现在栈顶的时候才能访问它, 因此有必要设置两个栈

    public static void PostOrder3_1(BinTree t){
            Stack<BinTree> s = new Stack<BinTree>();
            Stack<Integer> s2 = new Stack<Integer>();   
            Integer i = new Integer(1);        //第二个栈有0 1 俩元素,1表示第二次访问,0,表示第一次访问
            while(t!=null || !s.empty()){
                while(t!=null){               //一路压入左孩子
                    s.push(t);                  
                    s2.push(new Integer(0));
                    t = t.lchild;
                }
                //peek查看堆栈顶部的对象,但不从堆栈中移除它。
                while(!s.empty() && s2.peek().equals(i)){  //有两次访问的记录了,就出栈且输出啦,俩栈是同步pop的
                    s2.pop();
                    System.out.print(s.pop().date);
                }
                if(!s.empty()){         //第二次访问了,栈2的元素置为1,这回要指向右孩子
                    s2.pop();
                    s2.push(new Integer(1));
                    t = s.peek();
                    t = t.rchild;
                }
            }
        }
    View Code

    单栈法:

         * 先扫描跟节点的所有左节点并入栈,接着将栈顶元素出栈,
         * 再扫描该节点的右孩子节点并入栈,扫描右孩子的所有左节点并入栈,当一个节点的左右孩子均被访问后在访问该节点,
         * 这里用一个初始值为null的节点表示右子树刚刚被访问的
         * 一直到栈为空,这里的难点是如何判断右孩子已经被访问过了
         这个构造真的是太赞了!!

    public static void PostOrder3_2(BinTree t){
            BinTree q = null;
            Stack<BinTree> stack = new Stack<BinTree>();
            while(t!=null)
            {    
                while(t.lchild!=null)            //先左子树入栈,注意:这里最左节点(叶子)没有入栈,后面才入栈
                {
                    stack.push(t);
                    t = t.lchild;
                }
                while(t.rchild==null || t.rchild ==q )
                {            
                    System.out.print(t.date);   //当前节点无右孩子或者右孩子已经输出
                    q = t;                      //用来记录上一个节点
                    if(stack.empty())
                        return ;               //栈为空时,就结束程序
                    t = stack.pop();           //节点出栈
                }        
                stack.push(t);           //!1、叶子节点入栈,指向的r.child是null 2、处理右孩子
                t = t.rchild;
            }
        }
    View Code

    另外一个单栈实现,单栈的构造都好神奇!

    public static void PostOrder3_3(BinTree t){
            BinTree node = t, prev = t;
            Stack<BinTree> stack = new Stack<BinTree>();
            while(node != null || stack.size()>0)
            {
                while(node!=null)               //压入左孩子
                {
                    stack.push(node);
                    node = node.lchild;
                }
                if(!stack.empty())
                {
                    BinTree temp = stack.peek().rchild;  //记录当前节点的右孩子
                    if((temp == null)||temp == prev)
                    {
                        node = stack.pop();           //满足俩条件就出栈输出
                        System.out.print(node.date);
                        prev = node;               //标记已经被访问
                        node = null;               //把这个没用的节点置为null.,防止执行上面的while循环
                    } 
                    else{
                        node = temp;
                    }
                }        
            }
        }
    View Code

    如果用数组去实现stack的话,可以用这个,这个是在求解两节点个最近公共祖先时用到的(LCA算法),改写自考研书上

    //栈是用数组实现的 后序非递归算法,数组里含有所有的元素
        public static void  PostOrder3_4(BinTree t)
        {
                BinTree st[] = new BinTree[1000];        //用数组实现顺序栈    
                int flag,top=-1;                         //栈指针初始化
                BinTree p =t,q;
                do
                {
                    while(p!=null)                  //将p的所有左节点入栈
                    {
                        top++;
                        st[top] = p;
                        p = p.lchild;
                    } 
                    q = null;                       //q指向栈顶节点的前一个已经访问的节点
                    flag = 1;                       //设置flag = 1表示处理栈顶节点            
                    while(top !=-1 && flag ==1)
                    {
                        p = st[top];                //取出栈顶的元素
                        if(p.rchild==q)             //右孩子不存在或者右孩子已经被访问,访问之
                        {
                            System.out.print(p.date);
                            top--;
                            q=p;                    //q指向刚刚被访问的节点
                        }
                        else
                        {    
                            p = p.rchild;           //p指向右孩子节点
                            flag =0;                //flag=0表示栈顶节点处理完毕
                        }                        
                    }            
            }while(top!=-1);                       //栈不为空时循环
            System.out.println(); 
        }
    View Code

    下面是全部的代码外加一个测试实例:

    /***************************************
     * 时间:2013年12月2日
     * author:lm
     * 内容:二叉树前中后递归与非递归遍历,以及广度优先遍历
     ***************************************/
    import java.util.ArrayDeque;
    import java.util.Queue;
    import java.util.Stack;
    
    public class BinTree {
        private char date;
        private BinTree lchild;   //左孩子
        private BinTree rchild;   //右孩子
        
        private BinTree(char c ){
            date = c;
        }
        
        //先序遍历 递归实现  节点 - 左孩子 - 右孩子
        public static void preOrder1(BinTree t){
            if(t != null){
            System.out.print(t.date);
            preOrder1(t.lchild);
            preOrder1(t.rchild);
            }
        }
        
        //中序遍历 递归实现 左孩子  - 根结点 - 右孩子
        public static void InOrder2(BinTree t){
            if(t != null){    
            InOrder2(t.lchild);
            System.out.print(t.date);
            InOrder2(t.rchild);
            }
        }
        
        //后序遍历  递归实现  左孩子 - 右孩子 - 根结点
        public static void PostOrder3(BinTree t){
            if(t!=null)
            {
                PostOrder3(t.lchild);
                PostOrder3(t.rchild);
                System.out.print(t.date);
            }
        }
        
        /*前序非递归实现    节点 - 左孩子 - 右孩子
         1)访问结点P,并将结点P入栈;
         2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,
                  循环至1);若不为空,则将P的左孩子置为当前的结点P;
         3)直到P为NULL并且栈为空,则遍历结束。
         */
        
        public static void preOrder1_1(BinTree t){
            Stack<BinTree> s = new Stack<BinTree>();
            while(t != null || !s.empty()){       //只要满足节点非空或则栈为非空,就要不断遍历
                
                while(t !=null ){                 //压入所有左孩子节点,压入前输出
                    System.out.print(t.date);
                    s.push(t);
                    t = t.lchild;
                }
                if(!s.empty()){        //左孩子都搞定了,从栈顶取出一个节点,指向它的右孩子,继续前面while循环
                    t = s.pop();
                    t = t.rchild;
                }
            }
            System.out.println();
        }
        public static void preOrder1_2(BinTree t){
            Stack<BinTree> stack = new Stack<BinTree>();
            if(t!=null)
            {
                stack.push(t);
                while(!stack.empty())
                {
                    t = stack.pop();
                    System.out.print(t.date);
                    if(t.rchild!=null)         //先把右孩子压入栈,利用下压栈的特性
                        stack.push(t.rchild);
                    if(t.lchild!=null)
                        stack.push(t.lchild);                
                }
            }
        }
        
        /*中序非递归的实现  左孩子  - 根结点 - 右孩子
         * 访问任意一节点p,若其左孩子非空,p入栈,且p的左孩子作为当前节点,然后在对其进行同样的处理
         * 若其左孩子为空,则输出栈顶点元素并进行出栈操作,访问该栈顶的节点的右孩子
         * 直到p为null并且栈为空则遍历结束
         */
        public static void InOrder2_1(BinTree t){
            Stack<BinTree> s = new Stack<BinTree>();
            while(t != null || !s.empty()){
                while(t !=null){           //和前序的非递归有点像
                    s.push(t);
                    t = t.lchild;           //一路指向左孩子
                }
                if(! s.empty()){
                    t = s.pop();              //取栈顶元素并输出,并指向右孩子
                    System.out.print(t.date);
                    t = t.rchild;
                }
            }
            System.out.println();
        }
    
        /*后序非递归实现  这个有难度  因为要保证左孩子和右孩子都已经被访问过了才访问根节点
         * 对于任一节点p,先入栈,然后沿着其左子树往下收索,直到没有左子树的节点,此时该节点入栈
         * 但此时不能进行出栈访问,要检查右孩子,所以接下来一相同的规则进行处理,当访问其右孩子时,
         * 该节点又出现在栈顶,此时就可以出栈访问,这样就保证了正确的访问顺序,这个过程中,
         * 每个节点都两次出现在栈顶,只有在第二次出现在栈顶的时候才能访问它, 因此有必要设置两个栈
         */
        public static void PostOrder3_1(BinTree t){
            Stack<BinTree> s = new Stack<BinTree>();
            Stack<Integer> s2 = new Stack<Integer>();   
            Integer i = new Integer(1);        //第二个栈有0 1 俩元素,1表示第二次访问,0,表示第一次访问
            while(t!=null || !s.empty()){
                while(t!=null){               //压入左孩子
                    s.push(t);                  
                    s2.push(new Integer(0));
                    t = t.lchild;
                }
                //peek查看堆栈顶部的对象,但不从堆栈中移除它。
                while(!s.empty() && s2.peek().equals(i)){  //有两次访问的记录了,就出栈且输出啦,俩栈是同步pop的
                    s2.pop();
                    System.out.print(s.pop().date);
                }
                if(!s.empty()){         //第二次访问了,栈2的元素置为1,这回要指向右孩子
                    s2.pop();
                    s2.push(new Integer(1));
                    t = s.peek();
                    t = t.rchild;
                }
            }
        }
        /*
         * 先扫描跟节点的所有左节点并入栈,接着将栈顶元素出栈,
         * 再扫描该节点的右孩子节点并入栈,扫描右孩子的所有左节点并入栈,当一个节点的左右孩子均被访问后在访问该节点,
         * 这里用一个初始值为null的节点表示右子树刚刚被访问的
         * 一直到栈为空,这里的难点是如何判断右孩子已经被访问过了
         * 个人觉得这个构造真的是太赞了!!
         */
        public static void PostOrder3_2(BinTree t){
            BinTree q = null;
            Stack<BinTree> stack = new Stack<BinTree>();
            while(t!=null)
            {    
                while(t.lchild!=null)            //先左子树入栈,注意:这里最左节点(叶子)没有入栈,后面才入栈
                {
                    stack.push(t);
                    t = t.lchild;
                }
                while(t.rchild==null || t.rchild ==q )
                {            
                    System.out.print(t.date);   //当前节点无右孩子或者右孩子已经输出
                    q = t;                      //用来记录上一个节点
                    if(stack.empty())
                        return ;               //栈为空时,就结束程序
                    t = stack.pop();           //节点出栈
                }        
                stack.push(t);       //!奇葩 1、叶子节点入栈,指向的r.child是null 2、不满足while条件时,处理右孩子
                t = t.rchild;
            }
        }
        
        public static void PostOrder3_3(BinTree t){
            BinTree node = t, prev = t;
            Stack<BinTree> stack = new Stack<BinTree>();
            while(node != null || stack.size()>0)
            {
                while(node!=null)               //压入左孩子
                {
                    stack.push(node);
                    node = node.lchild;
                }
                if(!stack.empty())
                {
                    BinTree temp = stack.peek().rchild;  //记录当前节点的右孩子
                    if((temp == null)||temp == prev)
                    {
                        node = stack.pop();           //满足俩条件就出栈输出
                        System.out.print(node.date);
                        prev = node;               //标记已经被访问
                        node = null;               //把这个没用的节点置为null.,防止执行上面的while循环
                    } 
                    else{
                        node = temp;
                    }
                }        
            }
        }
        //栈是用数组实现的后序非递归算法,数组里含有所有的元素
        public static void  PostOrder3_4(BinTree t)
        {
                BinTree st[] = new BinTree[1000];        //用数组实现顺序栈    
                int flag,top=-1;                         //栈指针初始化
                BinTree p =t,q;
                do
                {
                    while(p!=null)                  //将p的所有左节点入栈
                    {
                        top++;
                        st[top] = p;
                        p = p.lchild;
                    } 
                    q = null;                       //q指向栈顶节点的前一个已经访问的节点
                    flag = 1;                       //设置flag = 1表示处理栈顶节点            
                    while(top !=-1 && flag ==1)
                    {
                        p = st[top];                //取出栈顶的元素
                        if(p.rchild==q)             //右孩子不存在或者右孩子已经被访问,访问之
                        {
                            System.out.print(p.date);
                            top--;
                            q=p;                    //q指向刚刚被访问的节点
                        }
                        else
                        {    
                            p = p.rchild;           //p指向右孩子节点
                            flag =0;                //flag=0表示栈顶节点处理完毕
                        }                        
                    }            
            }while(top!=-1);                       //栈不为空时循环
            System.out.println(); 
        }        
        
        /*广度优先遍历树,又叫层次遍历
         * 1.首先将根节点放入队列中。
           2.当队列为非空时,循环执行步骤3到步骤5,否则执行6;
           3.出队列取得一个结点,访问该结点;
           4.若该结点的左子树为非空,则将该结点的左子树入队列;
           5.若该结点的右子树为非空,则将该结点的右子树入队列;
           6.结束。
         */
        /*
         * 补充:队列是什么,提到队列,就要想到先进先进先出,即为先进先出队列,先进来的人先服务,我们日常生活
         * 排队时不是先到的人先接受服务么?
         */
        public static void BFSOrder(BinTree t)
        {
            if(t==null) return ;
            Queue<BinTree> queue = new ArrayDeque<BinTree>();    
            //队列小知识:使用offer和poll优于add和remove之处在于它们返回值可以判断成功与否,而不抛出异常
            queue.offer(t);              //进入队列
            while(!queue.isEmpty())
            {
                t=queue.poll();           //当前节点出队列
                System.out.print(t.date);
                if(t.lchild!=null)              //当前节点左孩子去排队,在前面哦
                    queue.offer(t.lchild);
                if(t.rchild!=null)            //右孩子排第二
                    queue.offer(t.rchild);    
            }
        }
        public static void main(String[] args) {
             BinTree b1 = new BinTree('a');
             BinTree b2 = new BinTree('b');
             BinTree b3 = new BinTree('c');
             BinTree b4 = new BinTree('d');
             BinTree b5 = new BinTree('e');
             BinTree b6 = new BinTree('f');
             BinTree b7 = new BinTree('g');
        
            /**
             *      a 
             *    /   
             *   b     c
             *  /    / 
             * d   e f   g
             */
            b1.lchild = b2;
            b1.rchild = b3;
            b2.lchild = b4;
            b2.rchild = b5;
            b3.lchild = b6;
            b3.rchild = b7;
        
            BinTree.preOrder1(b1);
            System.out.println();
            
            BinTree.preOrder1_1(b1);
            System.out.println();
            
            BinTree.InOrder2(b1);
            System.out.println();
            BinTree.InOrder2_1(b1);
            System.out.println();
            
            BinTree.PostOrder3(b1);
            System.out.println();
    
            BinTree.PostOrder3_1(b1);    
            System.out.println();
            BinTree.PostOrder3_2(b1);
            System.out.println();
            BinTree.PostOrder3_3(b1);
            System.out.println();
            BinTree.PostOrder3_4(b1);
            System.out.println();
            
            BinTree.BFSOrder(b1);
            System.out.println();    
            }
    }
    View Code

    网上有好的很好的文章都有写道这几个搜索算法,这个博客是用c++实现的

    http://www.blogjava.net/fancydeepin/archive/2013/02/03/cpp_binarytreesearch.html

  • 相关阅读:
    SQLServer 备份和还原
    Ling To DataTable
    asp.net安全防范
    SQLServer 定时备份(一)
    asp.net网站安全常见问题与防范
    SQLServer 定时备份(二)
    flex大法:一网打尽所有常见布局
    BetterScroll源码阅读顺便学习TypeScript
    快速搭建一个代码在线编辑预览工具
    一个简单标注库的插件化开发实践
  • 原文地址:https://www.cnblogs.com/LZYY/p/3454778.html
Copyright © 2011-2022 走看看