二叉树必知必会-基础篇(补充)
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树
二叉堆的实现
推荐↓↓↓
长
按
关
注
以上是关于二叉树必知必会-基础篇(补充)的主要内容,如果未能解决你的问题,请参考以下文章