二叉树必知必会-基础篇(补充)

作者: 视野跳动 | 来源:发表于2018-10-27 16:34 被阅读36次

    前言:中序和后序遍历迭代算法比较难,对于初学者需要多花一点时间去理解,同时需要注意学习方式。我个人看代码喜欢把代码放到相关软件里面,比如这里我使用的是CodeBlocks,这样比较有感觉。其次,边画图边理解是一种非常好的方法。

    二叉树必知必会-基础篇

    一、前序遍历迭代算法

    为了更好的说清楚遍历的迭代算法,决定用这张比较简单的图。
    不同于递归算法,迭代算法需要自己构建栈。所以在学习迭代算法之前需要对栈的概念有所了解。那什么是栈呢?简单的说栈是一个线性表,栈里面的元素具有线性关系,进入栈的操作也称为压栈或者入栈。类似手枪的弹夹往里面压入子弹,当取出子弹时,最先压入弹夹的子弹肯定是最后一个取出,所以栈中元素进出栈有个特性,先进后出,后进先出。

    前序遍历.jpg
    //递归算法系统已经自己构建好了栈,迭代算法则需要我们自己构建栈。
    public static void PreOrderTraverse(BiTree T){
              if(T == null)
                  return;
    
              Stack<BiTree> stack = new Stack<BiTree>();
              stack.push(T);
    
              while(!stack.isEmpty()){
                 BiTree cur = stack.pop();//取出栈顶元素
                 System.out.println(cur.value + " ");
                //关键点:要先压入右孩子,再压入左孩子,这样在出栈时会先打印左孩子再打印右孩子(栈:先进后出)
                if(cur.right != null){
                    stack.push(cur.right);
                  }
                if(cur.left != null){
                  stack.push(cur.left);
                }
          }
    
    }
    

    分析:新建一个栈 stack ,将上图A压入到了栈中,在循环中,首先取出了A,打印A,判断A是否有右孩子,将右孩子C压入栈中,继续判断A是否有左孩子,将左孩子B压入栈中,因为A在stack.pop()时已经弹栈了,所以现在栈里面有两个元素,最底层元素是C,倒数第二个元素是B。再次执行循环,取出栈顶元素(此时的栈顶元素就是B),打印B。同理,判断B是否有右孩子,发现B没有右孩子,无操作。继续判断B是否有左孩子,将D压入栈中(此时栈中还有两个元素,最底层的依然是C,倒数第二层的是D)。再次执行循环,取出了栈顶元素D,打印D。继续判断D是否有左右孩子,依次类推就可以打印出:ABDGHCEIF。

    二、中序遍历迭代算法

    中序遍历.jpg
     //中序遍历迭代算法
        void InOderTraverse(BiTree T){
            if(T==null)
                return;
    
            Stack<BiTree>  stack =  new Stack<BiTree>();
            BiTree cur = T;
            while(!stack.isEmpty || T!=null){
                if(cur != null){//循环遍历左子树
                    stack.push(cur);//将结点压入栈中
                    cur = cur.left;//指针指向本结点的左子树
                }else{
                    cur = stack.pop();//取出栈顶元素
                    System.out.println(cur.val+"");//打印cur的值
                    cur = cur.right;
                    }
                }
    
        }
    

    解析:
    while循环语句外cur已经被赋值,进入循环cur不等于null,执行if语句中的代码,将A压入栈中,并且修改cur指针。if执行完成以后再次循环cur仍然不等于null,就这样一直循环遍历左子树,左子树一直压栈,直到结点G时(此时栈中元素是ABDG),G没有左孩子,所以cur等于null,当再次进行while循环时,执行else中的语句,stack.pop()取出栈顶元素(栈先进后出,所以G是栈顶元素),此时就打印了第一个值G。cur.right发现G没有右孩子,所以cur还是null,再次循环又进入到else语句中,现在stack.pop()方法取出的是元素D。打印D,D是有右孩子的,所以此时的cur经过赋值以后是不为空的,下次循环将进入if语句中,会将H压入栈中。然后继续判断H是否有做孩子。以此类推,就实现了中序遍历。在本简书中实现的遍历方式只是比较常用的,实现方法也不止这一种!

    三、后序遍历迭代算法

    根据后序遍历的规则,可以知道后序遍历是左右中结构,例如G-H-D、还有E-F-C,最后遍历根结点。为了实现这种规则的遍历,新建了两个栈,详情写在了代码注释中。


    后序遍历.jpg
    void postorderTraversal(BiTree T){
             if(T== null)
                  return;
    
              Stack<BiTree> stack = new Stack<BiTree>();//第一个stack用于添加结点和它的左右孩子。
              // 因为需要左右中输出,还要进行翻转,所以第一个栈是中左右压入。翻转之后就是中右左,最后取出就是左右中了。
              Stack<BiTree> outstack = new Stack<BiTree>();//第二个outstack用于翻转第一个stack输出
    
              stack.push(T);
              while( !stack .isEmpty()){//确保所有元素都被翻转转移到第二个stack
                   BiTree cur = stack .pop();
                   outstack .push(cur);//把栈顶元素添加到第二个stack
    
                  if(cur.left != null){//把栈顶元素的左孩子添加入到第一个stack
                          stack .push(cur.left);
                        }
                  if(cur.right != null){//把栈顶元素的右孩子添加入到第一个stack
                        stack .push(cur.right);
                        }
        }
    
              while( !outstack .isEmpty()){//遍历输出第二个stack,即为后序遍历
                      System.out.println(outstack .pop().val+"");
                    }
    }
    

    分析:具体分析和上面的分析方法类似,不行你就画两个栈,一步步的来,多想想就OK了

    四、层序遍历迭代算法

    这里使用的是宽度优先遍历,什么是宽度优先遍历?是以离初状态的状态距离为序进行遍历。这是官方定义,我也一脸懵逼,宽度优先遍历说白了就是需要你自己维护一个队列。怎样维护的?好好体会下面例子。

    层序遍历.jpg
           //层序遍历二叉树迭代
            public static void levelTraversal(BiTree T){
                if(T==null)
                     return;
    
                 LinkedList<BiTree> stack = new LinkedList<BiTree>();
                 stack.push(T)
                 while(!stack.isEmpty){
                      TreeNode cur = stack.removeFirst();//removeFirst():移除此队列中的第一个元素并返回此列表的第一个元素。
                      System.out.print(cur.val+"");
                      if(cur.left != null){
                          stack.push(cur.left);
                        }
                      if(cur.right != null){
                          stack.push(cur.right);
                          }
                    }
    
            }
    

    分析:注意这里创建的是链表。(自己画图)注意removeFirst()方法的作用,经历了上面的洗礼,这里应该不难了,才对。

    五、总结

    到这里二叉树的基础部分就结束了,后面的内容多吗?多少并不重要,重要的是你已经在开始了。

    • 二叉树遍历(恭喜已经完成了)
    • 二叉搜索树
    • 二叉搜索树查找
    • 二叉搜索树插入和删除
    • AVL树
    • 二叉堆的实现
    放过我吧,我还是个孩子

    每月更新两篇,质量保证,点击关注!
    听说点喜欢的人运气会一直很好!!!

    相关文章

      网友评论

        本文标题:二叉树必知必会-基础篇(补充)

        本文链接:https://www.haomeiwen.com/subject/gxsrzftx.html