📜  递归 mdn - Javascript (1)

📅  最后修改于: 2023-12-03 15:28:22.532000             🧑  作者: Mango

递归 mdn - Javascript

递归是一种函数调用自身的技术。在编程中,递归函数能够解决许多复杂的问题。在 JavaScript 中,递归可以很好地解决一些树状结构的问题,如 DOM,以及某些算法问题。在本文中,我们将介绍什么是递归,以及如何在 JavaScript 中使用递归函数。

什么是递归

递归是一种函数调用自身的技术。递归函数通过调用自身来解决问题。实现递归的基本思想是将问题分解为更小的问题,递归调用函数解决更小的问题,并将结果合并以解决原始问题。

递归函数的关键是确保递归调用会停止,否则程序将进入无限循环。为了确保递归调用停止,我们需要至少有一个“基本情况”,通常是问题可以直接解决的情况。在递归函数中,基本情况通常是函数不再调用自身,而是返回结果的情况。

递归通常比迭代更简洁、易于理解和维护。然而,递归在某些情况下可能会带来性能问题,并且如果使用不正确,可能会导致栈溢出等问题。

递归算法

递归算法是指使用递归函数解决问题的算法。递归算法通常涉及遍历对象的属性或元素,解决复杂的数据结构问题,以及实现分治算法(divide and conquer)。

下面是一个简单的递归算法,用于计算一个数的阶乘:

function factorial(n) {
  if (n === 0) { // 基本情况
    return 1;
  } else { // 递归情况
    return n * factorial(n - 1);
  }
}

// 计算 5 的阶乘
factorial(5); // 120

在这个函数中,基本情况是当 n 等于 0 时返回 1。递归情况是当 n 不为 0 时,调用函数本身,并将 n 减去 1,最终返回 n 乘以调用函数返回的结果。

递归和栈

在 JavaScript 中,递归经常使用调用栈来执行函数的调用,并将函数的局部变量的值存储在其中。在递归调用中,每当函数被调用时,一个新的堆栈帧将被推入栈顶。当函数返回时,堆栈帧将被弹出栈顶,以便程序返回到上一级调用。

递归深度是指递归函数在栈中推入和弹出堆栈帧的次数。如果递归深度很大,可能会导致堆栈溢出,并导致程序崩溃。

由于递归需要使用堆栈,因此它在某些情况下可能会占用大量内存,并且可能导致性能问题。在这种情况下,使用迭代算法可能是更好的选择。

使用递归的例子

递归可以用于解决许多不同类型的问题,包括树形结构问题、排序问题和搜索问题。以下是其中的一些例子:

计算斐波那契数列

斐波那契数列是由 0 和 1 开始,随后每个数都是前两个数之和的数列。斐波那契数列的前几个数是 0、1、1、2、3、5、8、13、21、34 等。

function fibonacci(n) {
  if (n === 0 || n === 1) { // 基本情况
    return n;
  } else { // 递归情况
    return fibonacci(n - 1) + fibonacci(n - 2);
  }
}

// 计算斐波那契数列的第 10 个数
fibonacci(10); // 55

在这个函数中,基本情况是当 n 等于 0 或 1 时返回 n。递归情况是当 n 大于 1 时,调用函数本身,并将 n 减去 1 和 2,最终返回这两个调用函数返回的结果之和。

遍历 DOM 树

递归经常用于遍历 DOM 树和处理树形结构数据。以下是一个使用递归函数遍历 DOM 树的例子:

function traverse(node) {
  console.log(node.nodeName);
  for (let i = 0; i < node.childNodes.length; i++) {
    traverse(node.childNodes[i]);
  }
}

// 遍历整个页面的 DOM 树
traverse(document.documentElement);

在这个函数中,我们遍历给定节点的所有子节点,并调用 traverse 函数以遍历这些节点的子节点。这个遍历过程将递归执行,直到最后一个节点被访问。

实现快速排序算法

快速排序是一种常用的排序算法,通常使用递归实现。以下是一个使用递归函数实现快速排序的例子:

function quicksort(arr) {
  if (arr.length < 2) { // 基本情况
    return arr;
  } else { // 递归情况
    let pivot = arr[0];
    let less = arr.slice(1).filter((value) => value <= pivot);
    let greater = arr.slice(1).filter((value) => value > pivot);
    return quicksort(less).concat([pivot], quicksort(greater));
  }
}

// 排序数组
quicksort([34, 3, 23, 67, 12, 2, 74, 9]); // [2, 3, 9, 12, 23, 34, 67, 74]

在这个函数中,基本情况是当数组长度小于 2 时,返回原数组。递归情况是当数组长度大于等于 2 时,将数组中的第一个元素选为 pivot,并将数组拆分为比 pivot 小和比 pivot 大的两个子数组。然后,调用函数本身,对这两个子数组进行排序,并将其合并再与 pivot 组合。

总结

通过使用递归,我们可以解决许多复杂的问题。递归算法使代码更简洁、易于理解和维护,并且通常比迭代算法更具表现力。但是,递归也可能会带来性能问题,并且可能导致堆栈溢出等问题。因此,在使用递归时,我们必须仔细考虑如何确保递归调用会停止,以避免这些问题的出现。