🧑💻JavaScript算法与数据结构-HowieCong
务必要熟悉JavaScript使用再来学!
一、遍历的方式
按照顺序规则的不同,遍历方式有如下四种:
- 先序遍历
- 中序遍历
- 后序遍历
- 层次遍历
按照实现方式的不同,遍历方式又可以分为以下两种:
- 递归遍历(先,中,后序遍历)
- 迭代遍历(层次遍历)
二、递归遍历
编程语言中,函数Func(Type a,......)直接或间接调用函数本身,则该函数称为递归函数
简单来说,当我们看到一个函数反复调用它自己的时候,递归就发生了。递归 => 反复
递归式的定义:
- 可以没有根结点,作为一颗空树存在
- 如果不是空树,那必须由根节点、左子树和右子树组成,且左右子树都是二叉树
- 反复地执行“创建一个由数据域、左右子树组成地结点”这个动作,直到数据被分配完为止
Eg:在保证“左子树一定先于右子树遍历”,那么遍历的情况就这三种
- 根结点 => 左子树 => 右子树
- 左子树 => 根结点 => 右子树
- 左子树 => 右子树 => 根结点
这三种情况,分别对应二叉树的先序遍历,中序遍历和后序遍历
根结点的遍历分别被安排在了首要位置,中间位置和后序遍历
所谓的先序、中序和后序,其实就是根结点的遍历时机
三、遍历
(1)先序遍历
- 遍历路线如数字所示:
- 如果有N多个子树,那么我们在每一棵树内部,都要重复这个旅行路线:
const root = {
val: "A",
left: {
val: "B",
left: {
val:"D",
},
right:{
val: "E"
}
},
right:{
val:"C",
right:{
val:"F"
}
}
};
递归函数的编写要点:
- 递归式:每一次重复的内容是什么,这里要做先序遍历,那么每次重复的是就是根结点 -> 左子树 -> 右子树 这个路线
- 递归边界:什么时候停下来,在遍历的场景下,当我们发现遍历的目标树为空的时候,就意味着旅途已经达到终点了,在编码实现里对应着一个return语句
- 第一个递归遍历函数(先序遍历)
// 所有遍历函数的入参都是树的根结点对象
function preorder(root){
if(!root){
return
}
// 输出当前遍历的结点值
console.log('当前遍历的结点值为:',root.val)
// 递归遍历左子树
preorder(root.left)
// 递归遍历右子树
preorder(root.right)
}
图解先序遍历过程
- 调用preorder(root),这里root就是A,它非空,所以进入递归式,输出A值,接着优先遍历左子树preorder(root.left);此事为preorder(B)
- 进入preorder(B):入参为结点B,非空,进入递归式,输出B值。接着优先遍历B的左子树,preorder(root.left),此时为preorder(D):
- 进入preorder(D):入参为结点D,非空,进入递归式,输出D值,接着优先遍历D的左子树,preorder(root.legt),此时为preorder(null):
- 进入preoder(null),发现抵达了递归边界,直接return,然后是preorder(D)的逻辑往下走,走到了preorder(root.right):
- 再次进入preorder(null),发现抵达了递归边界,直接return掉,回到preorder(D)里,接着preoder(D)的逻辑往下走,发现preorder(D)已经执行完,于是返回,回到preorder(B),接着preorder(B)往下走,进入preorder(root.right),也就是preorder(E)
- E不为空,进入递归式,输出E值,接着优先遍历E的左子树,preorder(root.left),此时为preorder(null),抵达递归边界,直接返回preorder(E);继续preorder(E)执行下去,是preorder(root.right),这里E的right同样是null,直接返回。如此,preorder(E)就执行完了,回到preorder(B)里去,发现preorder(B)也执行完了,于是回到preorder(A);执行preorder(A)中的preorder(root.right)去了
- root是A,root.right就是C,进入preorder(C)的逻辑:
- C不为空,进入递归式,输出C值,接着优先遍历C的左子树,preorder(root.left),此时为preorder(null),触碰递归边界,直接返回,继续preorder(C)执行下去,就是preorder(root.right),这里是C的right是F:
- 进入preorder(F)的逻辑,F不为空,进入递归式,输出F值,接着优先遍历F的左子树,preorder(root.left),此时为preorder(null),触碰递归边界,直接返回preorder(F);继续执行preorder(F),是preorder(root.right),这里是F的right同样是null,故直接返回preorder(F),此时preorder(F)已经执行完了,返回preorder(C),此时preorder(C)也执行完了,返回preorder(A);发现preorder(A)作为递归入口,它的逻辑也已经执行完了,于是我们的递归活动就到这介结束了
- 结点值为:A => B => D => E => C => F
(2)中序遍历
- 理解了先序遍历的过程,中序遍历的过程区别只是把遍历顺序调换了
- 左子树 => 根结点 => 右子树
- 若有多个子树,遍历路线如下:
- 递归边界照旧,唯一发生变化的是递归式里调用递归函数的顺序——左子树的访问优先于根结点
// 所有遍历函数的入参都是树的根结点对象
function inorder(root){
// 递归边界,root为空
if(!root){
return
}
// 递归遍历左子树
inorder(root.left)
// 输出当前遍历的结点值
console.log('当前遍历的结点值为',root.val)
// 递归遍历右子树
inorder(root.right)
}
- 输出顺序:D => B => E => A => C => F
(3)后序遍历
- 遍历顺序:左子树 => 右子树 => 根结点
- 如果有多个子树,遍历路线如下:
- 编码实现时,递归边界照旧,唯一发生变化的仍然是递归式里调用递归函数的顺序:
function postorder(root)[
// 递归边界,root为空
if(!root){
return
}
// 递归遍历左子树
postorder(root.left)
// 递归遍历右子树
postorder(root.right)
// 输出当前遍历的结点值
consoloe.log('当前遍历的结点值为',root.val)
}
- 输出顺序:D => E => B => F => C => A
四、总结
对于二叉树的先中后遍历,只要掌握到一种思路,举一反三,顺手推导其他三种思路就好啦。一起加油!一起努力!
❓其他
1. 疑问与作者HowieCong声明
- 如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
- 若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
- 声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。