JS 中的广度与深度优先遍历

现在有一种类似树的数据结构,但是不存在共同的根节点 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 中查看。

原文地址:https://www.cnblogs.com/ygunoil/p/10562333.html

时间: 2024-11-15 05:55:28

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

js中数组与对象的遍历

数组遍历: 1.js基本的for遍历 2.jquery提供的each函数 ----------------------------------- $.each(array, function(){ alert(this); }); ----------------------------------- 对象遍历: js: --------------------------------------------- for (var k in obj) { alert(obj[k]): } jque

有向图的广度、深度优先遍历

基于List存储的邻接表,一个工具类,创建一个有向图: 代码如下: package com.daxin; import java.util.ArrayList; import java.util.List; /** * * @author daxin * * @email [email protected] * * @date 2017年9月24日 下午5:36:56 * */ class Node { public Node(int val) { this.val = val; } int v

各种图的创建以及广度,深度优先遍历(临接矩阵存储)

#include <stdio.h> #include <iostream> #include <limits.h> #include <queue> #define INFINTY INT_MAX //最大值 #define MaxVertexNum 100 //最大顶点数 using namespace std; typedef enum{DG,UDG,DN,UDN} GraphKind; //图的种类(有向图.无向图,又向网,无向网) typedef

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

之前遇到过这个问题,但是没找到问题的所在,将for(var i in  array){} 改成了for(var i ;i<array.length;i++)循环,但是今天又遇到了,mark一下错误. 由于需要将后台传过来的数据进行排序,所以我在这个页面里面的Array扩展了一个方法: 那么问题来了,当刷新页面的时候,common中的方法抛出异常, 遍历29次之后有遍历了一次自己扩展的方法.由于不是temp中的,所以多了一项underfined. 解决方法: (1).将for(var i in a

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

以下是老师作为数据结构课的作业的要求,没有什么实际用处和可以探讨和总结的的地方,所以简单代码直接展示. 宽度优先遍历: 1 #include<cstdio> 2 #include<iostream> 3 #include<cstring> 4 using namespace std; 5 6 #define _clr(x, y) memset(x, y, sizeof(x)) 7 #define N 1010 8 9 int head[N], tot; 10 struc

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

// =================== 求最大值===================================== <script> var arr = [10,35,765,21345,678,89]; var max = arr [0]; for (var i=0;i< arr.length;i++) { if (max<arr[i]){ max = arr [i]; } } console.log("最大值:" + max); </sc

js中数字的4种遍历方式

<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title>数组的遍历方式</title> <script type="text/javascript"> var arr = [11,22,33,55]; //普通的循环遍历方式 function first(){ for(var i= 0;i<arr.length;

js中数组遍历for与for in区别(强烈建议不要使用for in遍历数组)

转自: http://www.cnblogs.com/javaee6/p/4142270.html?utm_source=tuicool&utm_medium=referral js中遍历数组的有两种方式 1 2 3 4 5 6 7 8 9 var array=['a'] //标准的for循环 for(var i=1;i<array.length;i++){     alert(array[i]) } //foreach循环 for(var i in array){     alert(ar

js中遍历数组、对象的方式

1.标准的for循环遍历数组 var array = [1,2,3,4,5,6,7]; for (var i = 0; i < array.length; i) { console.log(i,array[i]); } 2.for in 遍历对象 for in 以任意顺序遍历一个对象的可枚举属性. 因此当迭代那些访问次序重要的 arrays 时用整数索引去进行 for 循环 (或者使用 Array.prototype.forEach() 或 for...of 循环) . 一般常用来遍历非数组的对