可视化讲解 深度优先遍历(DFT)
可视化讲解 深度优先遍历(DFT)
深度优先遍历, 刷过题的朋友应该都很熟悉了,难是不难,但是理解起来还是要费一些功夫的. 深度优先遍历的实现方法有递归和非递归两种, 这里我们用可视化的角度,讲解前一种: 递归的深度优先遍历.
为什么要以可视化的方式来讲解呢? 因为人是视觉的动物, 如果和你说 二叉树
或 栈
, 相信大家脑中出现的都是下面的图形:
而不是下面的代码:
// binary tree class Node { constructor(value, leftChild, rightChild) { this.value = value this.leftChild = leftChild this.rightChild = rightChild } } // stack const stack = new Array() stack.push(1) var topItem = stack.pop()
所以说, 人是视觉动物, 以图形可视化的方式来讲解问题往往能讲解的更清楚, 这也就是我写本文的缘由.
效果展示
为了可视化的讲解 深度优先遍历算法
, 笔者写了一个简单的网页, 实现的功能有:
- 用户可编辑进行深度遍历的二叉树
- 网页上给出了 JavaScript 版本的实现
- 点击
Start DFT
按钮, 用户将看到算法中用到的 二叉树 和 栈 都将动态 的展示在页面中,可以直观的看到代码运行过程中数据的变化
页面目前还在继续优化中, 让我们看看目前的效果:
可以看到,网页模拟了深度搜索时二叉树和栈的动态变化过程:
- 二叉树中当前遍历到的节点会变成红色;
- 栈中压入 (push)的节点为灰色;
- 栈中弹出 (pop) 的节点会变为红色, 然后消失;
其中页面左上角为初始遍历的二叉树数据, 用户可以修改二叉树数据后再次启动可视化深度优先遍历过程.
页面左下角为深度优先遍历的 javascript 实现版本,作为参考.
深度优先遍历简介
可视化分析之前,让我们先来简单看看实现深度优先搜索的代码:
export class Dft { constructor(rootNode, stepCallback) { this.rootNode = rootNode this.stepCallback = stepCallback } start() { if (!this.rootNode || !this.stepCallback) { return } const stack = [this.rootNode] while (stack.length !== 0) { const curNode = stack.pop() console.log(`current node: ${curNode.value}`) curNode.childrenNodes.forEach(element => { stack.push(element) }) } } } export class Node { constructor(value, childrenNodes = []) { this.value = value this.childrenNodes = childrenNodes } }
代码不长,让我们一步步看.
首先我们创建了一个栈 const stack = [this.rootNode]
, 并将根节点放入栈中.
接下来是一个while语句, 跳出循环的条件是 栈为空, 也就是代表我们遍历完了整棵树.
在循环中, 我们先将栈顶的节点弹出, 并打印出来, 表示我们已经遍历过了该节点. 然后将该节点的所有子节点压入栈中, 这就保障了我们下一个遍历到的点就是该节点的子节点. 重复该循环, 最后我们就可以看到, 二叉树的每个节点都按照深度搜索的顺序被打印了出来:
current node: 1 current node: 4 current node: 7 current node: 6 current node: 2 current node: 5 current node: 3
如果是对栈的使用比较熟悉的同学, 可能看到这里就已经明白原理了.
但是, 如果是对栈使用不是很熟悉的同学, 估计对代码的执行过程还是没有一个直观的认识, 那么让我们以更直观的方式看看这段代码是怎么运行的.
可视化讲解
第一步, 将根节点压入栈中:
接下来进入 while 循环, 每个循环都会将栈顶的节点弹出, 将其子节点压入栈中:
可以看出, 深度优先遍历利用了栈先进后出的特性, 使得对于每个节点, 都将在遍历该节点后,下一步遍历他的子节点, 由此完成深度优先的遍历:
最后
本文中的二叉树, 栈的可视化是笔者自己封装的 UI 组件, 只需简单的调用就可以将代码中数据结构以可视化的方式显示在页面中.
个人觉得这样的数据结构可视化可能会对代码的讲解有些帮助, 如果你也有这方面的需求的话, 不妨在下面留言告诉我, 我可以将这些 UI 组件封装一下方便有需要的人使用.
想继续了解 D3.js || 数据可视化 ?
这里是我的 D3.js 、 数据可视化 的 github 地址, 欢迎 start & fork :tada:
如果觉得不错的话, 不妨点击下面的链接关注一下 : )

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java基础之HashTable源码解析
Java集合源码解析系列 Java基础之HashMap源码解析 Java基础之LinkedHashMap源码解析 Java基础之ArrayList源码解析 Java基础之LinkedList源码解析 HashTable public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { /** * 结构和HashMap一样,也是数组+链表 * 下面的几个参数也是一样的作用 */ private transient Entry<?,?>[] table; private transient int count; private int threshold; private float loadFactor; private transient int modCount = 0; private static final long serialVersionUID = 142174675951228...
- 下一篇
《Java8实战》-第六章读书笔记(用流收集数据-01)
用流收集数据 收集器简介 收集器用作高级归约 归约和汇总 查找流中的最大值和最小值 汇总 连接字符串 广义的归约汇总 分组 多级分组 按子组收集数据 代码 用流收集数据 我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合。你可以把Java 8的流看作花哨又懒惰的数据集迭代器。它们支持两种类型的操作:中间操作(如 filter 或 map )和终端操作(如 count 、 findFirst 、 forEach 和 reduce )。中间操作可以链接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗流,以产生一个最终结果,例如返回流中的最大元素。它们通常可以通过优化流水线来缩短计算时间。 我们已经在前面用过了 collect 终端操作了,当时主要是用来把 Stream 中所有的元素结合成一个 List 。在本章中,你会发现 collect 是一个归约操作,就像 reduce 一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的Collector 接口来定义的,因此区分 Collection 、 C...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- 2048小游戏-低调大师作品
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程