深度优先遍历(DFS)和广度优先遍历(BFS)的实现与简单应用

实现

DFS(Depth First Search) 深度优先遍历

深度优先遍历是指从某个顶点出发, 首先访问这个顶点, 然后找出刚访问这个节点的第一个未被访问的子节点,然后再以此子节点为顶点, 继续找它的下一个顶点进行访问。重复此步骤,直至所有节点都被访问完为止。

例如有这样一棵节点树:

const nodes = {
  value: '1',
  children: [
    {
      value: '2',
      children: [{ value: '5', children: [{ value: '9' }] }]
    },
    {
      value: '3',
      children: [
        { value: '6', children: [{ value: '10' }] },
        { value: '7' }
      ]
    },
    {
      value: '4',
      children: [{ value: '8' }]
    }
  ]
}

在深度优先的情况下应当输出:
['1', '2', '5', '9', '3', '6', '10', '7', '4', '8']

递归实现

对于这类结构化数据,使用递归是比较容易理解的,我们只需要把每次递归的返回值添加到结果数组即可

// *类型定义,之后的代码不再设置
type VNode<T> = {
  value: T,
  children?: VNode<T>[]
}

// *递归实现
function dfsRecursion<T>(node: VNode<T>) {
  const values: T[] = [];

  if (node) {
    values.push(node.value);
    node.children?.forEach(item => {
      values.push(...dfsRecursion(item));
    })
  }

  return values;
}

非递归实现

非递归实现需要引入栈结构用于保存当前遍历的子树。由于栈是先进后出的结构,同时深度优先遍历是优先遍历每棵子树的第一棵子树,所以我们在压栈的的时候需要先将children逆序。

function dfsNonRecursion<T>(node: VNode<T>) {
  if (!node) return;

  const values: T[] = [];
  const stack = [node];

  while (stack.length !== 0) {
    const curNode = stack.pop();
    if (!curNode) break;

    values.push(curNode.value);

    curNode.children && stack.push(...curNode.children.slice().reverse());
  }

  return values;
}

BFS(Breath First Search) 广度优先遍历

广度优先遍历是将树按照层级层层遍历,直到某一层全部遍历完毕再遍历下一层级,直到所有节点全部遍历完成。

在广度优先的情况下,前文的树应当输出:

[ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]

非递归实现

广度优先遍历如果用递归来实现会比较麻烦,这里就展示非递归的实现。

深度优先遍历我们用的是来实现,而广度优先遍历需要用到的是队列。每当遍历到某棵子树,就将这颗子树的所有子树追加到队列中,以实现层层遍历。

function bfsNonRecursion<T>(node: VNode<T>) {
  if (!node) return;

  const values: T[] = [];
  const queue = [node];

  while (queue.length !== 0) {
    const curNode = queue.shift();
    if (!curNode) break;

    values.push(curNode.value);

    if (curNode.children) queue.push(...curNode.children);
  }

  return values;
}

用深度优先遍历思想实现深拷贝

我们的深拷贝接收一个item,并根据item的类型进行不同的处理,如果item是数组,则使用map方法生成新数组,并传入deepClone进行递归调用;如果item是对象,则遍历对象的每个成员,并对每个成员都递归调用deepClone,将处理程序交由下一次函数调用来处理;如果item是简单数据类型,则直接返回,不做处理。

function deepClone(item: any): any {
  if (Array.isArray(item)) return item.map(deepClone);

  if (Object.prototype.toString.call(item) === '[object Object]') {
    const obj: any = {};
    for (const key in item) {
      obj[key] = deepClone(item[key]);
    }
    return obj;
  }

  return item;
}

当然,这只是简单的实现,对于对象的循环引用等问题还需要做进一步的处理。

原文地址:https://www.cnblogs.com/hycstar/p/14785219.html