第 5 题:介绍下深度优先遍历和广度优先遍历,如何实现?

发布于 2022-10-11 23:24:26 字数 3148 浏览 239 评论 38

第五题问的是深度优先遍历和广度优先遍历,我是从 dom 节点的遍历来理解这个问题的。

我将用深度优先遍历和广度优先遍历对这个 dom 树进行查找。

深度优先遍历


深度优先遍历 DFS 与树的先序遍历比较类似。

假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和 v 有路径相通的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

/*深度优先遍历三种方式*/
let deepTraversal1 = (node, nodeList = []) => {
  if (node !== null) {
    nodeList.push(node)
    let children = node.children
    for (let i = 0; i < children.length; i++) {
      deepTraversal1(children[i], nodeList)
    }
  }
  return nodeList
}
let deepTraversal2 = (node) => {
    let nodes = []
    if (node !== null) {
      nodes.push(node)
      let children = node.children
      for (let i = 0; i < children.length; i++) {
        nodes = nodes.concat(deepTraversal2(children[i]))
      }
    }
    return nodes
  }
// 非递归
let deepTraversal3 = (node) => {
  let stack = []
  let nodes = []
  if (node) {
    // 推入当前处理的node
    stack.push(node)
    while (stack.length) {
      let item = stack.pop()
      let children = item.children
      nodes.push(item)
      // node = [] stack = [parent]
      // node = [parent] stack = [child3,child2,child1]
      // node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
      // node = [parent, child1-1] stack = [child3,child2,child1-2]
      for (let i = children.length - 1; i >= 0; i--) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}

输出结果

广度优先遍历


广度优先遍历 BFS

从图中某顶点 v 出发,在访问了 v 之后依次访问 v 的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得 先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。 如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

let widthTraversal2 = (node) => {
  let nodes = []
  let stack = []
  if (node) {
    stack.push(node)
    while (stack.length) {
      let item = stack.shift()
      let children = item.children
      nodes.push(item)
        // 队列,先进先出
        // nodes = [] stack = [parent]
        // nodes = [parent] stack = [child1,child2,child3]
        // nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
        // nodes = [parent,child1,child2]
      for (let i = 0; i < children.length; i++) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}

输出结果

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(38

谁把谁当真 2022-05-04 13:57:45

感觉广度优先遍历使用的stack 应该使用queue 更合适吧 就是先push进去的 先shift()取出来 放在nodes里

。谈场末日恋爱 2022-05-04 13:57:45

第五题问的是深度优先遍历和广度优先遍历,我是从dom节点的遍历来理解这个问题的

html代码

我将用深度优先遍历和广度优先遍历对这个dom树进行查找

深度优先遍历

深度优先遍历DFS 与树的先序遍历比较类似。
假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

/*深度优先遍历三种方式*/
let deepTraversal1 = (node, nodeList = []) => {
  if (node !== null) {
    nodeList.push(node)
    let children = node.children
    for (let i = 0; i < children.length; i++) {
      deepTraversal1(children[i], nodeList)
    }
  }
  return nodeList
}
let deepTraversal2 = (node) => {
    let nodes = []
    if (node !== null) {
      nodes.push(node)
      let children = node.children
      for (let i = 0; i < children.length; i++) {
        nodes = nodes.concat(deepTraversal2(children[i]))
      }
    }
    return nodes
  }
// 非递归
let deepTraversal3 = (node) => {
  let stack = []
  let nodes = []
  if (node) {
    // 推入当前处理的node
    stack.push(node)
    while (stack.length) {
      let item = stack.pop()
      let children = item.children
      nodes.push(item)
      // node = [] stack = [parent]
      // node = [parent] stack = [child3,child2,child1]
      // node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
      // node = [parent, child1-1] stack = [child3,child2,child1-2]
      for (let i = children.length - 1; i >= 0; i--) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}
输出结果

广度优先遍历

广度优先遍历 BFS
从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。 如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

let widthTraversal2 = (node) => {
  let nodes = []
  let stack = []
  if (node) {
    stack.push(node)
    while (stack.length) {
      let item = stack.shift()
      let children = item.children
      nodes.push(item)
        // 队列,先进先出
        // nodes = [] stack = [parent]
        // nodes = [parent] stack = [child1,child2,child3]
        // nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
        // nodes = [parent,child1,child2]
      for (let i = 0; i < children.length; i++) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}
输出结果

ps

仅个人理解,如果有错欢迎大家指出批评,一起进步

所有的方法都缺少对 node.chilren的非空判断,如果对象没有children属性则会报错

╄→承喏。◢ 2022-05-04 13:57:45

深度优先遍历


遍历结果是: 1->2->4->8->5->3->6->7
广度优先遍历

遍历结果是:1->2->3->4->5->6->7->8

星光不落少年眉 2022-05-04 13:57:45

宽度优先遍历 和 深度优先遍历 是 遍历 或者 搜索 图 和 树 的算法。

深度优先遍历: 从根节点开始,沿着树的深度遍历树的节点,尽可能深的搜索树的分支。沿着一条可能的路径一直往下走,深入到不能深入为止。【可以纵向优先搜索】

宽度优先遍历: 从根节点开始,沿着树的宽度遍历树的节点。横向一层(level)的去遍历。


一楼大兄弟的html代码:

  <div class="parent">
      <div class="child-1">
          <div class="child-1-1">
              <div class="child-1-1-1">
                a
              </div>
              <div class="child-1-1-2">
                b
              </div>              
          </div>
          <div class="child-1-2">
              <div class="child-1-2-1">
                c
              </div>    
          </div>     
          <div class="child-1-3">
                d
          </div>                 
      </div>
      <div class="child-2">
        <div class="child-2-1">
          e
        </div>
        <div class="child-2-2">
          f
        </div>        
      </div>
      <div class="child-3">
        <div class="child-3-1">
          g
        </div>
      </div>
  </div>

深度优先遍历 - 递归

    var dfs_recursive = function(root, res = []){
      res.push(root)
      for (let item of root.children) {
        dfs_recursive(item, res)
      }
      return res
    }
    console.log('1------------->>', dfs_recursive(root))

深度优先遍历 - stack 先进后出 【push(item) -> pop】 或者 [unshift(item) -> shift()]

    var dfs_stack = function(root) {
      const res = []
      const stack = []
      stack.push(root)
      while (stack.length != 0) {
        let item = stack.pop()
        res.push(item)
        for (let i = item.children.length - 1; i >= 0; i--) {
          stack.push(item.children[i])
        }
      }
      return res
    }
    console.log('2------------->>', dfs_stack(root))

广度优先遍历 - queue 先进先出[push(item) -> shift()] 或者[unshift(item) -> pop()]

    var bfs_queue = function(root) {
      const res = []
      const queue = []
      queue.push(root)
      while (queue.length != 0) {
        let currentLevelLength = queue.length
        for (let i = 0 ; i < currentLevelLength; i++) {
          let item = queue.shift()
          res.push(item)
          for (let j = 0; j < item.children.length; j++) {
            queue.push(item.children[j])
          }
        }
      }
      return res
    }
    console.log('3------------->>', bfs_queue(root))
呆橘 2022-05-04 13:57:45

深度遍历

var deep=(node,fn)=>{
  fn(node);
  if(node.children){
    for(let i=0;i<node.children.length;i++){
      deep(node.children[i],fn);
    }
  }
};

广度遍历

var wide=(node,fn)=>{
  var queue = [];
  queue.push(node);
  while (queue.length != 0) {
    let item = queue.shift();
    fn(item);
    for (let j = 0; j < item.children.length; j++) {
      queue.push(item.children[j])
    }
  }
};

测试代码

var parent=document.getElementsByClassName('parent')[0];
wide(parent,(node)=>{
  console.log(node.className);
});
deep(parent,(node)=>{
  console.log(node.className);
});
遮云壑 2022-05-04 13:57:45

我看大家广度优先都用两个数组做,其实用一个就可以了 ,利用for循环每次计算length的特性
广度优先遍历
`

function widthTraversal(node,nodeList = []){
    if(!node){
        return []
    }
    nodeList.push(node)
    for(let a = 0; a<nodeList.length;a++){
        let thisNode = nodeList[a]
        for(let i = 0; i< thisNode.children.length; i++){
            nodeList.push(thisNode.children[i])
        }
    }
    return nodeList
}

`

别在捏我脸啦 2022-05-04 13:57:45

我举个通俗的例子
假如你的面前摆了一排杯子,每个杯子里都装了不确定量的水。

深度优先遍历就是你拿起第一个杯子,喝光水,然后再拿起下一个杯子,喝光,一直到最后一个杯子。

广度优先遍历就是你从第一个杯子开始,每个杯子挨个都喝一口,然后又重头开始挨个喝一口,一直到全部喝完为止。

是这样吧?

冷心人i 2022-05-04 13:57:45

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充
对象的深拷贝

遍历树型结构对象啊

苏佲洛 2022-05-04 13:57:45
const data = [{
       name: 'a',
       children: [{
           name: 'a1',
           children: [{
               name: 'a11'
           }, {
               name: 'a12'
           }]
       }, {
           name: 'a2',
           children: [{
               name: 'a21'
           }, {
               name: 'a22'
           }]
       }, {
           name: 'a3',
           children: [{
               name: 'a31'
           }, {
               name: 'a32'
           }]
       }, ],
   }, {
       name: 'b',
       children: [{
           name: 'b1',
           children: [{
               name: 'b11'
           }, {
               name: 'b12'
           }]
       }, {
           name: 'b2',
           children: [{
               name: 'b21'
           }, {
               name: 'b22'
           }]
       }, {
           name: 'b3',
           children: [{
               name: 'b31'
           }, {
               name: 'b32'
           }]
       }, ],
   }];

   let result = [];
   // 深度遍历

   function deep(ary) {
       for (const val of ary) {
           result.push(val.name);
           if (Array.isArray(val.children)) {
               deep(val.children);
           }
       }
       return result.join(',');
   }
   // const str = deep(data);

   // 广度遍历

   function bfs(ary) {   // 对比了下楼上的,我发现我这运行效率有点低,233333
       let quee = [];
       for (let i = 0; i < ary.length; i++) {
           result.push(ary[i].name);
           if (Array.isArray(ary[i].children)) {
               quee.push(...ary[i].children);
               if (i === ary.length - 1) {
                   bfs(quee);
               }
           }
       }
       return result.join(',');
   }

   const str = bfs(data);
   console.log(str);
浸婚纱 2022-05-04 13:57:45

第五题问的是深度优先遍历和广度优先遍历,我是从dom节点的遍历来理解这个问题的

html代码

我将用深度优先遍历和广度优先遍历对这个dom树进行查找

深度优先遍历

深度优先遍历DFS 与树的先序遍历比较类似。
假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

/*深度优先遍历三种方式*/
let deepTraversal1 = (node, nodeList = []) => {
  if (node !== null) {
    nodeList.push(node)
    let children = node.children
    for (let i = 0; i < children.length; i++) {
      deepTraversal1(children[i], nodeList)
    }
  }
  return nodeList
}
let deepTraversal2 = (node) => {
    let nodes = []
    if (node !== null) {
      nodes.push(node)
      let children = node.children
      for (let i = 0; i < children.length; i++) {
        nodes = nodes.concat(deepTraversal2(children[i]))
      }
    }
    return nodes
  }
// 非递归
let deepTraversal3 = (node) => {
  let stack = []
  let nodes = []
  if (node) {
    // 推入当前处理的node
    stack.push(node)
    while (stack.length) {
      let item = stack.pop()
      let children = item.children
      nodes.push(item)
      // node = [] stack = [parent]
      // node = [parent] stack = [child3,child2,child1]
      // node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
      // node = [parent, child1-1] stack = [child3,child2,child1-2]
      for (let i = children.length - 1; i >= 0; i--) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}
输出结果

广度优先遍历

广度优先遍历 BFS
从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。 如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

let widthTraversal2 = (node) => {
  let nodes = []
  let stack = []
  if (node) {
    stack.push(node)
    while (stack.length) {
      let item = stack.shift()
      let children = item.children
      nodes.push(item)
        // 队列,先进先出
        // nodes = [] stack = [parent]
        // nodes = [parent] stack = [child1,child2,child3]
        // nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
        // nodes = [parent,child1,child2]
      for (let i = 0; i < children.length; i++) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}
输出结果

ps

仅个人理解,如果有错欢迎大家指出批评,一起进步

第二个广度和第一个不是一样的嘛

三生池水覆流年 2022-05-04 13:57:45

@atheist1
非递归版的 deepTraversal3 实际上是广度优先的

为啥是广度的啊?deepTraversal3 不是把当前节点的所有子节点遍历出来之后,在遍历下一个同胞节点吗?

没毛病的,循环是倒序。就是深度

静赏你的温柔 2022-05-04 13:57:45

@atheist1
非递归版的 deepTraversal3 实际上是广度优先的

获取项不一样 一个用到了pop 一个用shift

很糊涂小朋友 2022-05-04 13:57:45

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充
对象的深拷贝

DFS 可以用在查找两个 dom 节点的相同祖先节点

花海 2022-05-04 13:57:45

数组扁平化


const DFS = (arr, cb) => {
  const queue = []
  let idx = 0, length = arr.length - 1
  while(idx <= length) {
    if (Array.isArray(arr[idx])) {
      DFS(arr[idx], cb)
    } else {
      queue.push(arr[idx])
      cb(queue.shift())
    }
    idx++
  }
}

const BFS = (arr, cb) => {
  const queue = [arr]
  while(queue.length > 0) {
    let item = queue.shift()
    if (Array.isArray(item)) {
      for (let i = 0; i < item.length; i++) {
        queue.push(item[i])
      }
    } else {
      cb(item)
    }
  }
}

DFS([1, [2, 3, [4, 88]], [9, 45], 5, 6], (value) => {
  console.log('DFS value :>> ', value);
})

BFS([1, [2, 3, [4, 88]], [9, 45], 5, 6], (value) => {
  console.log('BFS value :>> ', value);
})



大姐,你呐 2022-05-04 13:57:45
var arr = [
    {
        label: '1',
        children: [
            {
                label: '1-1',
                children: null
            },
            {
                label: '1-2',
                children: [
                    { label: '1-2-1', children: null},
                    { label: '1-2-2', children: null},
                ]
            }
        ]
    },
    {
        label: '2',
        children: [
            {
                label: '2-1',
                children: [
                    { label: '2-2-1', children: null },
                    { label: '2-2-2', children: null },
                ]
            },
            {
                label: '2-2',
                children: [
                    { label: '2-2-1', children: null},
                ]
            }
        ]
    }
]

// 深度优先:
let nodeList = [];
function deepTraversal(arr) {
    for (const n of arr) {
        nodeList.push(n.label);
        if (n.children) deepTraversal(n.children);
    }
}
deepTraversal(arr)
console.log('nodeList', nodeList)

// 广度优先:
var nodeList2 = [];
function widthTraversal(arr) {
    let stack = [];
    for (const n of arr) {
        nodeList2.push(n.label);
        if (n.children) stack = [...stack, ...n.children]
    }
    if (stack.length) widthTraversal(stack);
}
widthTraversal(arr);
console.log('nodeList2', nodeList2)
活雷疯! 2022-05-04 13:57:45

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充 对象的深拷贝

我在业务中有用到过:
1、富文本“一键排版”
2、树形组件,拖动排序,数据递归处理等

万水千山粽是情ミ 2022-05-04 13:57:45

附上结果图:

class TreeNode {
  constructor(val, left = null, right = null) {
    this.val = val;
    this.left = left;
    this.right = right;
  }
}

const root = new TreeNode(
  1,
  new TreeNode(2, null, new TreeNode(4)),
  new TreeNode(3, new TreeNode(5))
);
console.log(root);

function dfs(root) {
  if (!root) return;
  console.log(root.val);
  dfs(root.left);
  dfs(root.right);
}
function bfs(root) {
  if (!root) return
  const queue = []
  queue.push(root)
  while(queue.length){
    let len = queue.length
    for(let i = 0; i < len; i++){
      let cur = queue.shift()
      console.log(cur.val);
      if(cur.left) queue.push(cur.left)
      if(cur.right) queue.push(cur.right)
    }
  }
}

console.log("------------------dfs-----------------");
dfs(root);
console.log("------------------bfs-----------------");
bfs(root);
薄荷→糖丶微凉 2022-05-04 13:57:44

@atheist1
非递归版的 deepTraversal3 实际上是广度优先的

这个是深度优先的,你看看它最后的循环,是从末尾开始,也就是说,再下一次循环进来的时候,每次出栈都是从后面最后一个开始[其实已经到了子节点的范畴了】,[(当前节点的子节点是被放在栈的后面)

水波映月 2022-05-04 13:57:39

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充
对象的深拷贝

深拷贝感觉是用到了深度优先, 广度优先,我还没写过...我得研究下

苄①跕圉湢 2022-05-04 13:57:39

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?
2.14补充
对象的深拷贝

深拷贝感觉是用到了深度优先, 广度优先,我还没写过...我得研究下

从掘金的广度深拷贝问题过来的,结果全是在聊遍历,自己试着实现了一下广度拷贝,不知道有没有更好的方式。

const isArray = (a)=>{Array.isArray(a)};
const isObject = (o)=>Object.prototype.toString.call(o) === '[object Object]';

/**
* 广度优先深拷贝,借助队列实现,每个子节点入队列时,记录下它的新父节点以及他的key
*/

const bfsCopy = (src)=>{
  const queue = [];
  // 入队列操作
  const inQueue = (o,parent) => {
    for(let key in o){
        queue.push({
          parent, // 新的父节点
          key,  // 子节点的key
          node: o[key] // 子节点
        })
    }
  }
  if(isArray(src) || isObject(src)){
    const root = isArray(src) ? [] : {};
    // 将根节点的子节点入队列
    inQueue(src,root);
    // 开始遍历队列
    while(queue.length !== 0){
      // 取出队头元素 
      const { parent, key , node } = queue.shift();
      if(isArray(node) || isObject(node)){
          const r  = isArray(node) ? [] : {};
          parent[key] = r;
          inQueue(node,r)
      }else{
          parent[key] = node;
      }
    }
    return root;
  }
  return src;
}
枕梦 2022-05-04 13:57:33

二叉树

var myTree = {
    val: 6,
    left: {
      val: 5, 
      left: { 
        val: 4 
      }, 
      right: { 
        val: 3 
      }
    },
    right: { 
      val: 2, 
      right: { 
        val: 1 
      } 
    }
}
广度优先遍历 BFS

思路是利用队列,从根节点开始,依次将左节点、右节点push进数组。

function bfs (tree) {
    var queue = [tree]
    var res = []
    var count = 0
    while (queue[count] && queue[count].val) {
        res.push(queue[count].val)
        var left = queue[count].left
        var right = queue[count].right
        if (left) {
            queue.push(left)
        }
        if (right) {
            queue.push(right)
        }
        count++
    }
    return res
}
深度优先遍历 DFS

思路是利用栈,从根节点开始,依次将右、左节点入栈。

// 非递归版本
function dfs (tree) {
    var stack = [tree]
    var res = []
    while (stack.length) {
        var node = stack.pop()
        res.push(node.val)
        var left = node.left
        var right = node.right
        if (right) stack.push(right)
        if (left) stack.push(left)
    }
    return res
}
山田美奈子 2022-05-04 13:57:18

第五题问的是深度优先遍历和广度优先遍历,我是从dom节点的遍历来理解这个问题的

html代码

我将用深度优先遍历和广度优先遍历对这个dom树进行查找

深度优先遍历

深度优先遍历DFS 与树的先序遍历比较类似。
假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

/*深度优先遍历三种方式*/
let deepTraversal1 = (node, nodeList = []) => {
  if (node !== null) {
    nodeList.push(node)
    let children = node.children
    for (let i = 0; i < children.length; i++) {
      deepTraversal1(children[i], nodeList)
    }
  }
  return nodeList
}
let deepTraversal2 = (node) => {
    let nodes = []
    if (node !== null) {
      nodes.push(node)
      let children = node.children
      for (let i = 0; i < children.length; i++) {
        nodes = nodes.concat(deepTraversal2(children[i]))
      }
    }
    return nodes
  }
// 非递归
let deepTraversal3 = (node) => {
  let stack = []
  let nodes = []
  if (node) {
    // 推入当前处理的node
    stack.push(node)
    while (stack.length) {
      let item = stack.pop()
      let children = item.children
      nodes.push(item)
      // node = [] stack = [parent]
      // node = [parent] stack = [child3,child2,child1]
      // node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
      // node = [parent, child1-1] stack = [child3,child2,child1-2]
      for (let i = children.length - 1; i >= 0; i--) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}
输出结果

广度优先遍历

广度优先遍历 BFS
从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。 如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

let widthTraversal2 = (node) => {
  let nodes = []
  let stack = []
  if (node) {
    stack.push(node)
    while (stack.length) {
      let item = stack.shift()
      let children = item.children
      nodes.push(item)
        // 队列,先进先出
        // nodes = [] stack = [parent]
        // nodes = [parent] stack = [child1,child2,child3]
        // nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
        // nodes = [parent,child1,child2]
      for (let i = 0; i < children.length; i++) {
        stack.push(children[i])
      }
    }
  }
  return nodes
}
输出结果

ps

仅个人理解,如果有错欢迎大家指出批评,一起进步

感谢楼主的回答,了解到了深度遍历和广度遍历区别,小白把代码放在codepen实现了一遍。https://codepen.io/valleylmh/pen/EBBrWg

忆沫 2022-05-04 13:57:13

深度优先:找到一个节点后,把它的后辈都找出来,最常用递归法。
广度优先:找到一个节点后,把他同级的兄弟节点都找出来放在前边,把孩子放到后边,最常用 while

不交电费瞎发啥光 2022-05-04 13:57:13

@atheist1
非递归版的 deepTraversal3 实际上是广度优先的

为啥是广度的啊?deepTraversal3 不是把当前节点的所有子节点遍历出来之后,在遍历下一个同胞节点吗?

明明#如月 2022-05-04 13:56:57

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充
对象的深拷贝

数组扁平化

凉城 2022-05-04 13:54:46

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?
2.14补充
对象的深拷贝

前端确实很少涉及算法。 这个我也只在做小游戏的时候有用到过

问下大佬,有个旧的json 格式转成新的json格式

var old ={
"颜色":[
{
"黄色":{
"尺码":[
{
"XL":{
"形状":[
{
"多边形":1
},
{
"方形":2
}
]
}
},
{
"M":{
"形状":[
{
"方形":3
}
]
}
}
]
}
},
{
"红色":{
"尺码":[
{
"XL":{
"形状":[
{
"多边形":1
},
{
"方形":2
}
]
}
},
{
"M":{
"形状":[
{
"方形":3
}
]
}
}
]
}
}
]
}

var new =[
{
// priceId: 1,
// price: 35.0,
// "stock": 8,
"attrValueList": [
{
"attrKey": "颜色",
"attrValue": "黄色",
"attrCode": "1001"
},
{
"attrKey": "尺码",
"attrValue": "xm",
"attrCode": "2001"
},
{
"attrKey": "形状",
"attrValue": "多边形",
"attrCode": "3001"
}

    ]
  },
  {
    // priceId: 1,
    // price: 35.0,
    // "stock": 8,
    "attrValueList": [
      {
        "attrKey": "颜色",
        "attrValue": "黄色",
        "attrCode": "1001"
      },
      {
        "attrKey": "尺码",
        "attrValue": "L",
        "attrCode": "2001"
      },
      {
        "attrKey": "形状",
        "attrValue": "多边形",
        "attrCode": "3001"
      }

    ]
  },
  {
    // priceId: 1,
    // price: 35.0,
    // "stock": 8,
    "attrValueList": [
      {
        "attrKey": "颜色",
        "attrValue": "黄色",
        "attrCode": "1001"
      },
      {
        "attrKey": "尺码",
        "attrValue": "L",
        "attrCode": "2001"
      },
      {
        "attrKey": "形状",
        "attrValue": "五边形",
        "attrCode": "3001"
      }

    ]
  }

]
有方便的转换方法吗

浅听莫相离 2022-05-04 13:54:15

generator +Interator接口实现深度遍历

function *DFS(tree){
    yield tree;
    let children=tree.children;
    if(children){
        for(let i in children){
            yield *DFS(children[i])
        }
    }
}
console.log([...DFS(tree)])
断桥再见 2022-05-04 13:53:57

写得非常好了,我之前也了解过这两个东西,@atheist1 ,代码通俗易懂,答案简单直观,非常棒

断肠人 2022-05-04 13:51:25


这个字打错了吧

初相遇 2022-05-04 13:50:10

@atheist1 宽搜还是写 queue 吧,stack 歧义太严重了

画离情绘悲伤 2022-05-04 13:42:31

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充
对象的深拷贝

这其实是和公司的具体业务相关的,我们公司把在业务层背后抽象了一套树形结构的领域对象模型,整个项目里少不了折腾树的递归,这时候发现熟练掌握深度遍历和广度遍历就十分有用了。

半山落雨半山空 2022-05-04 13:38:56

@caihg 我也是这样想的

眼眸里的快感 2022-05-04 11:11:19

@atheist1
非递归版的 deepTraversal3 实际上是广度优先的

您的好友蓝忘机已上羡 2022-05-04 11:10:08

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充
对象的深拷贝

Dom树的遍历

听你说爱我 2022-05-04 11:01:36

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?

2.14补充
对象的深拷贝

前端确实很少涉及算法。 这个我也只在做小游戏的时候有用到过

野心澎湃 2022-05-02 03:18:16

图是一种复杂的非线性结构,它由边(边Edge)和点(顶点Vertex)组成。一条边连接的两个点称为相邻顶点。

G = (V, E)

图分为:

  • 有向图
  • 无向图

本文探讨的是无向图

图的表示

图的表示一般有以下两种:

  • 邻接矩阵:使用二维数组来表示点与点之间是否有边,如 arr[i][j] = 1表示节点 i 与节点 j 之间有边,arr[i][j] = 0表示节点 i 与节点 j 之间没有边
  • 邻接表:邻接表是图的一种链式储存结构,这种结构类似树的子链表,对于图中的每一个顶点Vi,把所有邻接于Vi的顶点Vj链成一个单链表,这个单链表就是顶点Vi的邻接表,单链表一般由数组或字典结构表示。

创建图

下面声明图类,Vertex 用数组结构表示,Edge 用 map结构表示

function Graph() {
    this.vertices = [] // 顶点集合
    this.edges = new Map() // 边集合
}
Graph.prototype.addVertex = function(v) { // 添加顶点方法
    this.vertices.push(v)
    this.edges.set(v, [])
}
Graph.prototype.addEdge = function(v, w) { // 添加边方法
    let vEdge = this.edges.get(v)
    vEdge.push(w)
    let wEdge = this.edges.get(w)
    wEdge.push(v)
    this.edges.set(v, vEdge)
    this.edges.set(w, wEdge)
}
Graph.prototype.toString = function() {
    var s = ''
    for (var i=0; i<this.vertices.length; i++) {
        s += this.vertices[i] + ' -> '
        var neighors = this.edges.get(this.vertices[i])
        for (var j=0; j<neighors.length; j++) {
            s += neighors[j] + ' '
        }
        s += 'n'
    }
    return s
}

测试:

var graph = new Graph()
var vertices = [1, 2, 3, 4, 5]
for (var i=0; i<vertices.length; i++) {
    graph.addVertex(vertices[i])
}
graph.addEdge(1, 4); //增加边
graph.addEdge(1, 3);
graph.addEdge(2, 3);
graph.addEdge(2, 5);

console.log(graph.toString())
// 1 -> 4 3 
// 2 -> 3 5 
// 3 -> 1 2 
// 4 -> 1 
// 5 -> 2

测试成功

图的遍历

两种遍历算法:

  • 深度优先遍历
  • 广度优先遍历

深度优先遍历(DFS)

深度优先遍历(Depth-First-Search),是搜索算法的一种,它沿着树的深度遍历树的节点,尽可能深地搜索树的分支。当节点v的所有边都已被探寻过,将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已探寻源节点到其他所有节点为止,如果还有未被发现的节点,则选择其中一个未被发现的节点为源节点并重复以上操作,直到所有节点都被探寻完成。

简单的说,DFS就是从图中的一个节点开始追溯,直到最后一个节点,然后回溯,继续追溯下一条路径,直到到达所有的节点,如此往复,直到没有路径为止。

DFS 可以产生相应图的拓扑排序表,利用拓扑排序表可以解决很多问题,例如最大路径问题。一般用堆数据结构来辅助实现DFS算法。

注意:深度DFS属于盲目搜索,无法保证搜索到的路径为最短路径,也不是在搜索特定的路径,而是通过搜索来查看图中有哪些路径可以选择。

步骤:

  • 访问顶点v
  • 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问
  • 若此时途中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到所有顶点均被访问过为止

实现:

Graph.prototype.dfs = function() {
    var marked = []
    for (var i=0; i<this.vertices.length; i++) {
        if (!marked[this.vertices[i]]) {
            dfsVisit(this.vertices[i])
        }
    }
    
    function dfsVisit(u) {
        let edges = this.edges
        marked[u] = true
        console.log(u)
        var neighbors = edges.get(u)
        for (var i=0; i<neighbors.length; i++) {
            var w = neighbors[i]
            if (!marked[w]) {
                dfsVisit(w)
            }
        }
    }
}

测试:

graph.dfs()
// 1
// 4
// 3
// 2
// 5

测试成功

广度优先遍历(BFS)

广度优先遍历(Breadth-First-Search)是从根节点开始,沿着图的宽度遍历节点,如果所有节点均被访问过,则算法终止,BFS 同样属于盲目搜索,一般用队列数据结构来辅助实现BFS

BFS从一个节点开始,尝试访问尽可能靠近它的目标节点。本质上这种遍历在图上是逐层移动的,首先检查最靠近第一个节点的层,再逐渐向下移动到离起始节点最远的层

步骤:

  • 创建一个队列,并将开始节点放入队列中
  • 若队列非空,则从队列中取出第一个节点,并检测它是否为目标节点
    • 若是目标节点,则结束搜寻,并返回结果
    • 若不是,则将它所有没有被检测过的字节点都加入队列中
  • 若队列为空,表示图中并没有目标节点,则结束遍历

实现:

Graph.prototype.bfs = function(v) {
    var queue = [], marked = []
    marked[v] = true
    queue.push(v) // 添加到队尾
    while(queue.length > 0) {
        var s = queue.shift() // 从队首移除
        if (this.edges.has(s)) {
            console.log('visited vertex: ', s)
        }
        let neighbors = this.edges.get(s)
        for(let i=0;i<neighbors.length;i++) {
            var w = neighbors[i]
            if (!marked[w]) {
                marked[w] = true
                queue.push(w)
            }
        }
    }
}

测试:

graph.bfs(1)
// visited vertex:  1
// visited vertex:  4
// visited vertex:  3
// visited vertex:  2
// visited vertex:  5

测试成功

本文始发于我的博客:深度优先遍历与广度优先遍历

嘴硬脾气大 2022-05-01 17:30:14

概念上我觉得没啥问题,具体在业务上有啥应用的地方吗(尤其是前端)?


2.14补充
对象的深拷贝

~没有更多了~

关于作者

韵柒

暂无简介

0 文章
0 评论
24 人气
更多

推荐作者

胡图图

文章 0 评论 0

zt006

文章 0 评论 0

z祗昰~

文章 0 评论 0

冰葑

文章 0 评论 0

野の

文章 0 评论 0

天空

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文