现在有一种类似树的数据结构,但是不存在共同的根节点 root,每一个节点的结构为 {key: 'one', value: '1', children: [...]},都包含 key 和 value,如果存在 children 则内部会存在 n 个和此结构相同的节点,现模拟数据如下图:

已知一个 value 如 3-2-1,需要取出该路径上的所有 key,即期望得到 ['three', 'three-two', 'three-two-one']

1.广度优先遍历

广度优先的算法如下图:

从上图可以轻易看出广度优先即是按照数据结构的层次一层层遍历搜索。
首先需要把外层的数据结构放入一个待搜索的队列(Queue)中,进而对这个队列进行遍历,当正在遍历的节点存在子节点(children)时则把此子节点下所有节点放入待搜索队列的末端。
因为本需求需要记录路径,因此还需要对这些数据做一些特殊处理,此处采用了为这些节点增加 parent 即来源的方法。

对此队列依次搜索直至找到目标节点时,可通过深度遍历此节点的 parent 从而获得到整个目标路径。具体代码如下:

  1.  
    // 广度优先遍历
  2.  
    function findPathBFS(source, goal) {
  3.  
    // 深拷贝原始数据
  4.  
    var dataSource = JSON.parse(JSON.stringify(source))
  5.  
    var res = []
  6.  
    // 每一层的数据都 push 进 res
  7.  
    res.push(...dataSource)
  8.  
    // res 动态增加长度
  9.  
    for (var i = 0; i < res.length; i++) {
  10.  
    var curData = res[i]
  11.  
    // 匹配成功
  12.  
    if (curData.value === goal) {
  13.  
    var result = []
  14.  
    // 返回当前对象及其父节点所组成的结果
  15.  
    return (function findParent(data) {
  16.  
    result.unshift(data.key)
  17.  
    if (data.parent) return findParent(data.parent)
  18.  
    return result
  19.  
    })(curData)
  20.  
    }
  21.  
    // 如果有 children 则 push 进 res 中待搜索
  22.  
    if (curData.children) {
  23.  
    res.push(...curData.children.map(d => {
  24.  
    // 在每一个数据中增加 parent,为了记录路径使用
  25.  
    d.parent = curData
  26.  
    return d
  27.  
    }))
  28.  
    }
  29.  
    }
  30.  
    // 没有搜索到结果,默认返回空数组
  31.  
    return []
  32.  
    }

2.深度优先遍历

深度优先的算法如下图:

深度优先即是取得要遍历的节点时如果发现有子节点(children) 时,则不断的深度遍历,并把这些节点放入一个待搜索的栈(Stack)中,直到最后一个没有子节点的节点时,开始对栈进行搜索。后进先出(下列代码中使用了 push 方法入栈,因此需使用 pop 方法出栈),如果没有匹配到,则删掉此节点,同时删掉父节点中的自身,不断重复遍历直到匹配为止。注意,常规的深度优先并不会破坏原始数据结构,而是采用 isVisited 或者颜色标记法进行表示,原理相同,此处简单粗暴做了删除处理。代码如下:

  1.  
    // 深度优先遍历
  2.  
    function findPathDFS(source, goal) {
  3.  
    // 把所有资源放到一个树的节点下,因为会改变原数据,因此做深拷贝处理
  4.  
    var dataSource = [{children: JSON.parse(JSON.stringify(source))}]
  5.  
    var res = []
  6.  
    return (function dfs(data) {
  7.  
    if (!data.length) return res
  8.  
    res.push(data[0])
  9.  
    // 深度搜索一条数据,存取在数组 res 中
  10.  
    if (data[0].children) return dfs(data[0].children)
  11.  
    // 匹配成功
  12.  
    if (res[res.length - 1].value === goal) {
  13.  
    // 删除自己添加树的根节点
  14.  
    res.shift()
  15.  
    return res.map(r => r.key)
  16.  
    }
  17.  
    // 匹配失败则删掉当前比对的节点
  18.  
    res.pop()
  19.  
    // 没有匹配到任何值则 return
  20.  
    if (!res.length) return res
  21.  
    // 取得最后一个节点,待做再次匹配
  22.  
    var lastNode = res[res.length - 1]
  23.  
    // 删除已经匹配失败的节点(即为上面 res.pop() 的内容)
  24.  
    lastNode.children.shift()
  25.  
    // 没有 children 时
  26.  
    if (!lastNode.children.length) {
  27.  
    // 删除空 children,且此时需要深度搜索的为 res 的最后一个值
  28.  
    delete lastNode.children
  29.  
    return dfs([res.pop()])
  30.  
    }
  31.  
    return dfs(lastNode.children)
  32.  
    })(dataSource)
  33.  
    }

该方法在思考时,添加了根节点以把数据转换成树,并在做深度遍历时传入了子节点数组 children 作为参数,其实多有不便,于是优化后的代码如下:

  1.  
    // 优化后的深度搜索
  2.  
    function findPathDFS(source, goal) {
  3.  
    // 因为会改变原数据,因此做深拷贝处理
  4.  
    var dataSource = JSON.parse(JSON.stringify(source))
  5.  
    var res = []
  6.  
    return (function dfs(data) {
  7.  
    res.push(data)
  8.  
    // 深度搜索一条数据,存取在数组 res 中
  9.  
    if (data.children) return dfs(data.children[0])
  10.  
    // 匹配成功
  11.  
    if (res[res.length - 1].value === goal) {
  12.  
    return res.map(r => r.key)
  13.  
    }
  14.  
    // 匹配失败则删掉当前比对的节点
  15.  
    res.pop()
  16.  
    // 没有匹配到任何值则 return,如果源数据有值则再次深度搜索
  17.  
    if (!res.length) return !!dataSource.length ? dfs(dataSource.shift()) : res
  18.  
    // 取得最后一个节点,待做再次匹配
  19.  
    var lastNode = res[res.length - 1]
  20.  
    // 删除已经匹配失败的节点(即为上面 res.pop() 的内容)
  21.  
    lastNode.children.shift()
  22.  
    // 没有 children 时
  23.  
    if (!lastNode.children.length) {
  24.  
    // 删除空 children,且此时需要深度搜索的为 res 的最后一个值
  25.  
    delete lastNode.children
  26.  
    return dfs(res.pop())
  27.  
    }
  28.  
    return dfs(lastNode.children[0])
  29.  
    })(dataSource.shift())
  30.  
    }

改进后的方法只关心传入的节点,如果存在子节点则内部自行处理,而非预先传入所有子节点数组进行处理,此方法更易理解一些。

结语

以上便是广度与深度遍历在 JS 中的应用,代码可在 codepen 中查看。

JS 中的广度与深度优先遍历的更多相关文章

  1. [PHP] 算法-邻接矩阵图的广度和深度优先遍历的PHP实现

    1.图的深度优先遍历类似前序遍历,图的广度优先类似树的层序遍历 2.将图进行变形,根据顶点和边的关系进行层次划分,使用队列来进行遍历 3.广度优先遍历的关键点是使用一个队列来把当前结点的所有下一级关联 ...

  2. 在js中使用for和forEach遍历数组

    数组的遍历 for var arr = [1, 2, 3, 4]; for (var i = 0; i < arr.length; i++){ arr[i]; } forEach var arr ...

  3. js中数组的循环与遍历forEach,map

    对于前端的循环遍历我们知道有 针对js数组的forEach().map().filter().reduce()方法 针对js对象的for/in语句(for/in也能遍历数组,但不推荐) 针对jq数组/ ...

  4. JavaScript基础&实战(5)js中的数组、forEach遍历、Date对象、Math、String对象

    文章目录 1.工厂方法创建对象 1.1 代码块 1.2.测试结果 2.原型对象 2.1 代码 2.2 测试结果 3.toString 3.1 代码 3.2 测试结果 4.数组 4.1 代码 5.字面量 ...

  5. 图的广度、深度优先遍历 C语言

    以下是老师作为数据结构课的作业的要求,没有什么实际用处和可以探讨和总结的的地方,所以简单代码直接展示. 宽度优先遍历: #include<cstdio> #include<iostr ...

  6. 关于js中的for(var in)遍历属性报错问题

    之前遇到过这个问题,但是没找到问题的所在,将for(var i in  array){} 改成了for(var i ;i<array.length;i++)循环,但是今天又遇到了,mark一下错 ...

  7. JS中数组与对象的遍历方法实例小结

    一.数组的遍历: 首先定义一个数组 1 arr=['snow','bran','king','nightking']; 1.for循环,需要知道数组的长度; 2.foreach,没有返回值,可以不知道 ...

  8. JS中数组实现(倒序遍历数组,数组连接字符串)

    // =================== 求最大值===================================== <script> var arr = [10,35,765 ...

  9. js中数字的4种遍历方式

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title> ...

随机推荐

  1. 开发流程(Vue)

    1.当你拿到一个需求时,先不要着急完成静态页面 2.重点观察数据结构,进行数据的分析,包括前端所需要的数据类型从而进行数据类型定义(如果是前后端分离的情况下,建议不要考虑前端数据和数据库的数据类型对应 ...

  2. GDI+案例

    // // request_handler.cpp // ~~~~~~~~~~~~~~~~~~~ // // Copyright (c) 2003-2017 Christopher M. Kohlho ...

  3. 生成并下载txt类型的文件

    public ActionResult Index() { return View(); } /// <summary> /// 获取网页源码,并将其写入txt文件中,下载到本地 /// ...

  4. Vue系列之 => 路由的嵌套

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  5. 给学习立个flag

    今天是2018年7月7号,此时的砖相比昨天格外烫手,望着手套因被磨破而露出来的半截手指头,一股股热浪溜溜的从指间划过,背后还有小山一样高的砖头,感觉对面today店里的冰镇西瓜又成了不可奢望的梦... ...

  6. oracle 死锁

    oracle 死锁 --查用户名,查客户端机器 SELECT distinct s.username,s.MACHINE, s.sid||','||s.serial# FROM gv$session ...

  7. vue运行时 eslint 报“import/first” WARN deprecated browserslist 问题解决

    vue运行时 eslint 报“import/first”  WARN deprecated browserslist 问题解决 这个信息的意思是导入文件顺序不对,绝对导入应该放在相对导入前面.将绝对 ...

  8. IP通信基础学习第三周(上)

    TCP的连接情况有:同时打开,同时关闭,拒绝连接,异常终止连接. TCP流量控制的折中方法是滑动窗口协议,且TCP标准强烈不赞成发送窗口沿向后缩回. 在滑动窗口中,当A发送了11个字节的数据时,P3- ...

  9. Java爬虫模拟登录——不给我毛概二的H某大学

    你的账号访问太频繁,请一分钟之后再试! 从大一开始 就用脚本在刷课 在专业课踢的只剩下一门C#的情况下 活活刷到一周的课 大二开始教务系统多了一个非常**的操作 退课池 and 访问频繁缓冲 难道,我 ...

  10. 通过修改DNS达到不FQ也能访问Google(2018-12-25至现在已失效)

    一.前言 不知道各位小伙伴们现在用的搜索引擎是用Google搜索还是百度搜索呢?但我个人还是比较极力推荐用Google搜索的,首先用百度搜索后的结果的前几项大部分是满屏的广告,甚至搜索的结果并不能直接 ...