返回

JavaScript算法系列——树:详解递归与非递归实现,分步解析必出面试题(一)

前端

前言

在计算机科学领域,算法和数据结构是两大基石。本系列文章将聚焦于JavaScript算法,深入剖析各种常见算法的实现和应用。本篇第一部分,我们首先探索“树”这一重要数据结构,并重点介绍递归和非递归两种实现方式。

树的数据结构

树是一种非线性的数据结构,它由一个根节点和若干个子节点组成。每个子节点都可以继续拥有自己的子节点,形成一个层级结构。树结构广泛应用于各种场景,例如文件系统、XML文档和数据库索引。

递归与非递归实现

遍历树结构有两种主要方式:递归和非递归。

递归

递归是一种通过在函数内部调用自身来解决问题的技术。递归遍历树时,我们从根节点开始,然后依次遍历它的所有子节点。如果某个子节点本身也是一个树,则我们递归地遍历该子树。

// 递归深度优先遍历
function dfs(node) {
  if (node === null) {
    return;
  }
  // 对当前节点进行操作
  console.log(node.value);
  // 递归遍历子节点
  dfs(node.left);
  dfs(node.right);
}

非递归

非递归避免了函数的嵌套调用,而是使用栈数据结构来模拟递归的过程。我们从根节点开始,将其压入栈中。然后,依次弹出栈顶元素并对其进行操作,同时将其子节点压入栈中。

// 非递归深度优先遍历
function dfsNonRecursive(node) {
  const stack = [];
  while (node || stack.length) {
    if (node) {
      // 将当前节点压入栈中
      stack.push(node);
      // 继续遍历左子节点
      node = node.left;
    } else {
      // 如果当前节点为空,说明左子节点已遍历完毕,弹出栈顶元素
      node = stack.pop();
      // 对栈顶元素进行操作
      console.log(node.value);
      // 继续遍历右子节点
      node = node.right;
    }
  }
}

深度优先遍历(DFS)与广度优先遍历(BFS)

DFS和BFS是两种遍历树的经典算法。DFS优先遍历某一分支上的所有节点,然后再遍历其他分支。而BFS则逐层遍历树结构,每一层上的所有节点都遍历完毕后再继续下一层。

DFS

DFS的递归实现非常简洁,因为它只需调用自身。非递归实现虽然使用了栈,但由于栈的先进后出的特性,依然能够实现与递归相同的效果。

BFS

BFS的实现需要使用队列数据结构。我们从根节点开始,将其加入队列。然后,依次从队列中取出元素并对其进行操作,同时将它的子节点加入队列。

// 非递归广度优先遍历
function bfs(node) {
  const queue = [];
  if (node) {
    queue.push(node);
  }
  while (queue.length) {
    // 从队列中取出队首元素
    const currentNode = queue.shift();
    // 对当前节点进行操作
    console.log(currentNode.value);
    // 将当前节点的子节点加入队列
    if (currentNode.left) {
      queue.push(currentNode.left);
    }
    if (currentNode.right) {
      queue.push(currentNode.right);
    }
  }
}

结语

本篇文章深入探讨了JavaScript算法中“树”的数据结构,并详细介绍了递归和非递归两种遍历实现方式,以及DFS和BFS两种遍历算法。通过对这些概念的理解,你将能够解决各种与树结构相关的算法问题。在接下来的文章中,我们将继续探索其他重要算法,帮助你提升你的编程技能。