【金秋打卡】第14天 JavaScript版数据结构与算法-树形结构概念(二叉树的遍历)
2022/11/8 3:23:57
本文主要是介绍【金秋打卡】第14天 JavaScript版数据结构与算法-树形结构概念(二叉树的遍历),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
课程名称: JavaScript版数据结构与算法
课程章节:第8章 数据结构之“树”
课程讲师: lewis
课程内容:前端JS的算法基础
课程介绍:
8-1 树简介
8-2 深度与广度优先遍历
8-3 二叉树的先中后序遍历
8-4 二叉树的先中后序遍历(非递归版)
8-5 LeetCode:104. 二叉树的最大深度
8-6 LeetCode:111. 二叉树的最小深度
8-7 LeetCode:102. 二叉树的层序遍历
8-8 LeetCode:94. 二叉树的中序遍历
8-9 LeetCode:112. 路径总和
8-10 前端与树:遍历 JSON 的所有节点值
二叉树的遍历
不管是深度优先遍历还是广度优先遍历,都可以用递归算法或者非递归算法来实现,一般情况下递归算法比较容易实现,并且看起来直观,但是由于涉及到层层的函数栈调用,性能不高,而非递归算法有着比较好的性能。文章下面对深度优先遍历和广度优先遍历都做了算法描述和递归/非递归实现
首先我们定义这样的一个二叉树结构:
var nodes = { node: 6, left: { node: 5, left: { node: 4 }, right: { node: 3 } }, right: { node: 2, right: { node: 1 } } }
下面所有的代码都将对上述数据结构进行遍历。
1.1 深度优先遍历二叉树。
1.1.1 先序遍历(DLR)的算法:
递归算法
若二叉树为空,则算法结束,否则:
访问根结点;
前序遍历根结点的左子树;
前序遍历根结点的右子树。
var result = [] var dfs = function(nodes) { if(nodes.node) { result.push(nodes.node) nodes.left && dfs(nodes.left) nodes.right && dfs(nodes.right) } } dfs(nodes) console.log(result) // [6, 5, 4, 3, 2, 1]
非递归算法
初始化一个栈,将根节点压入栈中;
当栈为非空时,循环执行步骤3到4,否则执行结束;
出队列取得一个结点,访问该结点;
若该结点的右子树为非空,则将该结点的右子树入栈,若该结点的左子树为非空,则将该结点的左子树入栈;
var dfs = function(nodes) { var result = [] var stack = [] stack.push(nodes) while (stack.length) { var item = stack.pop() result.push(item.node) item.right && stack.push(item.right) item.left && stack.push(item.left) } return result } console.log(dfs(nodes)) // [6, 5, 4, 3, 2, 1]
1.1.2 中序遍历(LDR)的算法:
递归算法
若二叉树为空,则算法结束;否则:
中序遍历根结点的左子树;
访问根结点;
中序遍历根结点的右子树;
var result = [] var dfs = function(nodes) { if(nodes.node) { nodes.left && dfs(nodes.left) result.push(nodes.node) nodes.right && dfs(nodes.right) } } dfs(nodes) console.log(result) // [4, 5, 3, 6, 2, 1]
非递归算法
初始化一个栈,将根节点压入栈中,并标记为当前节点(item);
当栈为非空时,执行步骤3,否则执行结束;
如果当前节点(item)有左子树且没有被 touched,则执行4,否则执行5;
对当前节点(item)标记 touched,将当前节点的左子树赋值给当前节点(item=item.left) 并将当前节点(item)压入栈中,回到3;
清理当前节点(item)的 touched 标记,取出栈中的一个节点标记为当前节点(item),并访问,若当前节点(item)的右子树为非空,则将该结点的右子树入栈,回到3;
var dfs = function(nodes) { var result = [] var stack = [] var item = nodes stack.push(nodes) while (stack.length) { if(item.left && !item.touched) { item.touched = true item = item.left stack.push(item) continue } item.touched && delete item.touched // 清理标记 item = stack.pop() result.push(item.node) item.right && stack.push(item.right) } return result } console.log(dfs(nodes)) // [4, 5, 3, 6, 2, 1]
1.1.3 后序遍历(LRD)的算法:
递归算法:
若二叉树为空,则算法结束,否则:
后序遍历根结点的左子树;
后序遍历根结点的右子树;
访问根结点。
var result = [] var dfs = function(nodes) { if(nodes.node) { nodes.left && dfs(nodes.left) nodes.right && dfs(nodes.right) result.push(nodes.node) } } dfs(nodes) console.log(result) // [4, 3, 5, 1, 2, 6]
非递归算法:
初始化一个栈,将根节点压入栈中,并标记为当前节点(item);
当栈为非空时,执行步骤3,否则执行结束;
如果当前节点(item)有左子树且没有被 touched,则执行4,如果被 touched left 但没有被 touched right 则执行5 否则执行6;
对当前节点(item)标记 touched left,将当前节点的左子树赋值给当前节点(item=item.left) 并将当前节点(item)压入栈中,回到3;
对当前节点(item)标记 touched right,将当前节点的右子树赋值给当前节点(item=item.right) 并将当前节点(item)压入栈中,回到3;
清理当前节点(item)的 touched 标记,弹出栈中的一个节点并访问,然后再将栈顶节点标记为当前节点(item),回到3;
var dfs = function(nodes) { var result = [] var stack = [] var item = nodes stack.push(nodes) while (stack.length) { if(item.left && !item.touched) { item.touched = 'left' item = item.left stack.push(item) continue } if(item.right && item.touched !== 'right') { item.touched = 'right' item = item.right stack.push(item) continue } var out = stack.pop() out.touched && delete out.touched // 清理标记 result.push(out.node) item = stack.length ? stack[stack.length - 1] : null } return result } console.log(dfs(nodes)) // [4, 3, 5, 1, 2, 6]
1.2 广度优先遍历二叉树:
广度优先遍历二叉树(层序遍历)是用队列来实现的,从二叉树的第一层(根结点)开始,自上至下逐层遍历;在同一层中,按照从左到右的顺序对结点逐一访问。
按照从根结点至叶结点、从左子树至右子树的次序访问二叉树的结点。步骤:
初始化一个队列,并把根结点入列队;
当队列为非空时,循环执行步骤3到4,否则执行结束;
出队列取得一个结点,访问该结点;
若该结点的左子树为非空,则将该结点的左子树入队列,若该结点的右子树为非空,则将该结点的右子树入队列;
递归算法:
var result = [] var queue = [nodes] var bfs = function(count) { count = count || 0 if(queue[count]) { result.push(queue[count].node) var left = queue[count].left var right = queue[count].right if(left) { queue.push(left) } if(right) { queue.push(right) } bfs(++count) } } bfs() console.log(result) // [6, 5, 2, 4, 3, 1]
非递归算法
var bfs = function(nodes) { var result = [] var queue = [] var pointer = 0 queue.push(nodes) while(pointer < queue.length) { var item = queue[pointer++] // 这里不使用 shift 方法(复杂度高),用一个指针代替 result.push(item.node) console.log(item.node) item.left && queue.push(item.left) item.right && queue.push(item.right) } return result } console.log(bfs(nodes)) // [6, 5, 2, 4, 3, 1]
二、总结
在实际生产过程中尽量避免递归的调用,应为其时间复杂度,树和图是数据结构中最基础和最重要的组成部分,其经典应用还有红黑树,平衡二叉树等经典问题。
这篇关于【金秋打卡】第14天 JavaScript版数据结构与算法-树形结构概念(二叉树的遍历)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-24Java中定时任务实现方式及源码剖析
- 2024-11-24Java中定时任务实现方式及源码剖析
- 2024-11-24鸿蒙原生开发手记:03-元服务开发全流程(开发元服务,只需要看这一篇文章)
- 2024-11-24细说敏捷:敏捷四会之每日站会
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解