这是我参与更文挑战的第3天,活动详情查看: 更文挑战
前言
身为前端开发人员,平时项目中写代码用的最多的就是 数组、集合、对象。这3种数据结构,但是作为程序员这肯定时不够的。但是在我们看博客、源码解读都会看到深度优先遍历, 广度优先遍历、前序、中序、后序。所以理解它势在必行哇,本篇文章大概会带你用JS去模拟树形结构、用递归、或者是栈、或者是队列,去遍历树。 本篇文章代码在我的github 上。
定义
在计算机科学中,树是一种重要的非线性数据结构,直观的看,它是数据元素按分支关系组织起来的结构。二叉树是每个节点最多有两个子树的有序树。通常子树的根被称作“左子树”和“右子树”。二叉树常被用做二叉查找树和二叉堆或是二叉排序树。二叉树的每个节点至多只有两颗子树,二叉树有左右之分,次序不能颠倒。
二叉树有两个子树,对应js就是两个指针,还有个初始的值。OK 我们接下来用代码去模拟这个过程。
这样我们就去模拟了树的节点,OK我们接下来就构造一棵树,然后去遍历一颗树。
前序、中序、后序遍历的两种方式
假设前提都是先遍历左子树的前提下, 前中后其实就是对应根节点在的位置
前序遍历 按照的是 根节点 - 左子树 - 右子树
中序遍历按照的是 左子树 - 根节点 - 右节树
后序遍历按照的是 左子树- 右子树 - 根节点
递归版本
解读一下这段代码, total 存在的意义就是为了保持所有数据,在递归调用的时候能够保存。 递归结束的条件 就是当调用的节点是叶子节点 return 就好了。 然后不断回溯就可以获得相应的结果。
栈版本
上面二叉树的3种遍历方式本质函数递归,递归就是函数调用自己,对于js 而言会维护一个函数调用栈, 每调用一次函数,就会将当前函数的执行上下文push到栈中,然后等待函数执行结束就会pop出来。所以递归的本质其实栈。
所以不断地去模拟入栈出栈这个过程。
其实这边比较难理解的是中序遍历吧,其实他也是模拟递归的过程, 前序和后序都是从个根节点所在的位置去推理。
DFS 和 BFS
dfs 和 bfs 分别是深度优先遍历 和 广度优先遍历
dfs 深度优先遍历其实本质 就是前序遍历,大家把前面两种遍历方式认真去理解一遍就是非常OK的。 我主要和大家探讨下广度优先遍历, 何为广度优先遍遍历就是一层一层去遍历, 这里我们可以利用队列的 先进先出的特点带大家去领略其中的奥妙。
如果要你记录每一层的数据呢,其实就是bfs 的拓展? 二叉树从根节点开始每一层至少有0-2 个节点,不断往下每一层的节点就是不断乘以2的(假设每个节点都有左右两个) 所以呢我们肯定要循环的往队列的加东西不然循环的个数 如何确立
const levelOrder = function(root) {
// 初始化结果数组
const res = []
// 处理边界条件
if(!root) {
return res
}
// 初始化队列
const queue = []
// 队列第一个元素是根结点
queue.push(root)
// 当队列不为空时,反复执行以下逻辑
while(queue.length) {
// level 用来存储当前层的结点
const level = []
// 缓存刚进入循环时的队列长度,这一步很关键,因为队列长度后面会发生改变
const len = queue.length
// 循环遍历当前层级的结点
for(let i=0;i<len;i++) {
// 取出队列的头部元素
const top = queue.shift()
// 将头部元素的值推入 level 数组
level.push(top.val)
// 如果当前结点有左孩子,则推入下一层级
if(top.left) {
queue.push(top.left)
}
// 如果当前结点有右孩子,则推入下一层级
if(top.right) {
queue.push(top.right)
}
}
// 将 level 推入结果数组
res.push(level)
}
// 返回结果数组
return res
};
求树的深度
求树的高度,也就是递归左子树和右子树哪一个的深度,但是也不要忘记自身根节点+1。
翻转二叉树(二叉树的镜像)
二叉树的镜像其实就是在递归过程中二叉树的左右节点互换,然后就OK了哈哈哈哈哈。
总结
暂时先写这么多吧,写多了怕大家难以掌握,第一篇主要是树的 三种遍历方式,加上广度优先遍历掌握,这个掌握 了,其实其他二叉树的题目都是纸老虎,主要体会递归的回溯的魅力, 以及栈和队列这个数据结构,多写写你会发现数据结构和算法的关联性还是很大的,继续加油吧💪! 本篇例子都在我的github 欢迎star, 码子不易, 觉得对你有收获的可以点个👍不。