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

Posted 数据结构与算法

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树必知必会-基础篇(补充)相关的知识,希望对你有一定的参考价值。


作者丨绝代锋华
https://www.jianshu.com/p/d9a4eb82977e


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



一、前序遍历迭代算法


为了更好的说清楚遍历的迭代算法,决定用这张比较简单的图。


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


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


//递归算法系统已经自己构建好了栈,迭代算法则需要我们自己构建栈。
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。


二叉树必知必会-基础篇(补充)二、中序遍历迭代算法


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


//中序遍历迭代算法
    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,最后遍历根结点。为了实现这种规则的遍历,新建了两个栈,详情写在了代码注释中。


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


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了


二叉树必知必会-基础篇(补充)四、层序遍历迭代算法


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


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


//层序遍历二叉树迭代
        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树


二叉堆的实现


 推荐↓↓↓ 

以上是关于二叉树必知必会-基础篇(补充)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 0145. 二叉树的后序遍历:二叉树必会算法

SQL必知必会|SQL基础篇

能够自平衡的红黑树,必知必会

泥瓦匠聊并发编程:线程与多线程必知必会(基础篇)

iOS 必知必会 - APNs篇

Linux 编程之信号篇:异常监控必知必会