JavaScript中递归与回溯怎么应用

发布时间:2022-07-28 09:25:14 作者:iii
来源:亿速云 阅读:162

JavaScript中递归与回溯怎么应用

目录

  1. 引言
  2. 递归的基本概念
  3. 回溯的基本概念
  4. 递归与回溯的关系
  5. 递归的应用场景
  6. 回溯的应用场景
  7. 递归与回溯的结合应用
  8. 递归与回溯的优化
  9. 常见问题与解决方案
  10. 总结

引言

在编程中,递归和回溯是两种非常重要的算法思想。它们在解决许多复杂问题时表现出色,尤其是在处理树形结构、图结构以及组合问题时。JavaScript作为一种灵活且功能强大的编程语言,提供了良好的支持来实现递归和回溯算法。本文将深入探讨递归与回溯的基本概念、应用场景、优化方法以及常见问题的解决方案。

递归的基本概念

什么是递归

递归是一种通过函数调用自身来解决问题的方法。它通常用于解决那些可以分解为相同问题的子问题的情况。递归的核心思想是将一个大问题分解为若干个相同的小问题,直到这些小问题足够简单,可以直接解决。

递归的基本结构

一个典型的递归函数通常包含两个部分:

  1. 基准条件(Base Case):这是递归的终止条件,当满足这个条件时,递归将停止。
  2. 递归条件(Recursive Case):这是递归的核心部分,函数在这里调用自身,并将问题分解为更小的子问题。
function recursiveFunction(parameters) {
  // 基准条件
  if (baseCaseCondition) {
    return baseCaseResult;
  }
  
  // 递归条件
  return recursiveFunction(modifiedParameters);
}

递归的优缺点

优点:

缺点:

回溯的基本概念

什么是回溯

回溯是一种通过尝试所有可能的解来解决问题的算法。它通常用于解决组合问题、排列问题、子集问题等。回溯的核心思想是通过递归的方式尝试所有可能的解,并在发现当前解不满足条件时回退到上一步,尝试其他可能的解。

回溯的基本结构

一个典型的回溯算法通常包含以下几个步骤:

  1. 选择:在当前状态下选择一个可能的解。
  2. 递归:递归地尝试下一个状态。
  3. 撤销:如果当前解不满足条件,撤销选择,回退到上一步。
function backtrack(parameters) {
  // 基准条件
  if (baseCaseCondition) {
    // 处理结果
    return;
  }
  
  // 尝试所有可能的选择
  for (let choice of choices) {
    // 做出选择
    makeChoice(choice);
    
    // 递归
    backtrack(modifiedParameters);
    
    // 撤销选择
    undoChoice(choice);
  }
}

回溯的优缺点

优点:

缺点:

递归与回溯的关系

递归和回溯是两种紧密相关的算法思想。回溯算法通常通过递归来实现,因为回溯的核心思想是通过递归地尝试所有可能的解,并在发现当前解不满足条件时回退到上一步。因此,回溯算法可以看作是递归的一种特殊应用。

递归的应用场景

阶乘计算

阶乘是一个经典的递归问题。阶乘的定义是:n! = n * (n-1) * (n-2) * … * 1。我们可以通过递归的方式来计算阶乘。

function factorial(n) {
  if (n === 0 || n === 1) {
    return 1;
  }
  return n * factorial(n - 1);
}

console.log(factorial(5)); // 输出 120

斐波那契数列

斐波那契数列是另一个经典的递归问题。斐波那契数列的定义是:F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)。

function fibonacci(n) {
  if (n === 0) {
    return 0;
  }
  if (n === 1) {
    return 1;
  }
  return fibonacci(n - 1) + fibonacci(n - 2);
}

console.log(fibonacci(10)); // 输出 55

树的遍历

树的遍历是递归的典型应用场景。常见的树遍历方式包括前序遍历、中序遍历和后序遍历。

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

function preOrderTraversal(root) {
  if (root === null) {
    return;
  }
  console.log(root.value);
  preOrderTraversal(root.left);
  preOrderTraversal(root.right);
}

function inOrderTraversal(root) {
  if (root === null) {
    return;
  }
  inOrderTraversal(root.left);
  console.log(root.value);
  inOrderTraversal(root.right);
}

function postOrderTraversal(root) {
  if (root === null) {
    return;
  }
  postOrderTraversal(root.left);
  postOrderTraversal(root.right);
  console.log(root.value);
}

图的遍历

图的遍历也可以通过递归来实现。常见的图遍历方式包括深度优先搜索(DFS)和广度优先搜索(BFS)。

class Graph {
  constructor() {
    this.adjacencyList = new Map();
  }

  addVertex(vertex) {
    this.adjacencyList.set(vertex, []);
  }

  addEdge(vertex1, vertex2) {
    this.adjacencyList.get(vertex1).push(vertex2);
    this.adjacencyList.get(vertex2).push(vertex1);
  }

  dfs(startVertex) {
    const visited = new Set();
    this._dfs(startVertex, visited);
  }

  _dfs(vertex, visited) {
    visited.add(vertex);
    console.log(vertex);
    for (let neighbor of this.adjacencyList.get(vertex)) {
      if (!visited.has(neighbor)) {
        this._dfs(neighbor, visited);
      }
    }
  }
}

回溯的应用场景

八皇后问题

八皇后问题是一个经典的回溯问题。目标是在一个8x8的棋盘上放置8个皇后,使得它们互不攻击。

function solveNQueens(n) {
  const result = [];
  const board = Array.from({ length: n }, () => Array(n).fill('.'));
  
  function backtrack(row) {
    if (row === n) {
      result.push(board.map(row => row.join('')));
      return;
    }
    
    for (let col = 0; col < n; col++) {
      if (isSafe(row, col)) {
        board[row][col] = 'Q';
        backtrack(row + 1);
        board[row][col] = '.';
      }
    }
  }
  
  function isSafe(row, col) {
    for (let i = 0; i < row; i++) {
      if (board[i][col] === 'Q') {
        return false;
      }
    }
    
    for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
      if (board[i][j] === 'Q') {
        return false;
      }
    }
    
    for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
      if (board[i][j] === 'Q') {
        return false;
      }
    }
    
    return true;
  }
  
  backtrack(0);
  return result;
}

console.log(solveNQueens(8));

数独求解

数独求解是另一个经典的回溯问题。目标是在一个9x9的棋盘上填入数字,使得每一行、每一列和每一个3x3的子网格都包含1到9的数字。

function solveSudoku(board) {
  function backtrack(row, col) {
    if (row === 9) {
      return true;
    }
    
    if (col === 9) {
      return backtrack(row + 1, 0);
    }
    
    if (board[row][col] !== '.') {
      return backtrack(row, col + 1);
    }
    
    for (let num = 1; num <= 9; num++) {
      if (isValid(row, col, num)) {
        board[row][col] = num.toString();
        if (backtrack(row, col + 1)) {
          return true;
        }
        board[row][col] = '.';
      }
    }
    
    return false;
  }
  
  function isValid(row, col, num) {
    for (let i = 0; i < 9; i++) {
      if (board[row][i] === num.toString() || board[i][col] === num.toString()) {
        return false;
      }
    }
    
    const startRow = Math.floor(row / 3) * 3;
    const startCol = Math.floor(col / 3) * 3;
    for (let i = startRow; i < startRow + 3; i++) {
      for (let j = startCol; j < startCol + 3; j++) {
        if (board[i][j] === num.toString()) {
          return false;
        }
      }
    }
    
    return true;
  }
  
  backtrack(0, 0);
  return board;
}

const board = [
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
];

console.log(solveSudoku(board));

组合问题

组合问题是指从一组元素中选取若干个元素,使得它们满足一定的条件。回溯算法非常适合解决这类问题。

function combine(n, k) {
  const result = [];
  
  function backtrack(start, path) {
    if (path.length === k) {
      result.push([...path]);
      return;
    }
    
    for (let i = start; i <= n; i++) {
      path.push(i);
      backtrack(i + 1, path);
      path.pop();
    }
  }
  
  backtrack(1, []);
  return result;
}

console.log(combine(4, 2));

排列问题

排列问题是指从一组元素中选取若干个元素,使得它们的排列顺序满足一定的条件。回溯算法也非常适合解决这类问题。

function permute(nums) {
  const result = [];
  
  function backtrack(path) {
    if (path.length === nums.length) {
      result.push([...path]);
      return;
    }
    
    for (let num of nums) {
      if (!path.includes(num)) {
        path.push(num);
        backtrack(path);
        path.pop();
      }
    }
  }
  
  backtrack([]);
  return result;
}

console.log(permute([1, 2, 3]));

递归与回溯的结合应用

全排列问题

全排列问题是指从一组元素中选取所有元素,使得它们的排列顺序满足一定的条件。回溯算法非常适合解决这类问题。

function permuteUnique(nums) {
  const result = [];
  nums.sort((a, b) => a - b);
  
  function backtrack(path, used) {
    if (path.length === nums.length) {
      result.push([...path]);
      return;
    }
    
    for (let i = 0; i < nums.length; i++) {
      if (used[i] || (i > 0 && nums[i] === nums[i - 1] && !used[i - 1])) {
        continue;
      }
      used[i] = true;
      path.push(nums[i]);
      backtrack(path, used);
      path.pop();
      used[i] = false;
    }
  }
  
  backtrack([], Array(nums.length).fill(false));
  return result;
}

console.log(permuteUnique([1, 1, 2]));

子集问题

子集问题是指从一组元素中选取若干个元素,使得它们满足一定的条件。回溯算法非常适合解决这类问题。

function subsets(nums) {
  const result = [];
  
  function backtrack(start, path) {
    result.push([...path]);
    
    for (let i = start; i < nums.length; i++) {
      path.push(nums[i]);
      backtrack(i + 1, path);
      path.pop();
    }
  }
  
  backtrack(0, []);
  return result;
}

console.log(subsets([1, 2, 3]));

迷宫问题

迷宫问题是指在一个迷宫中找到从起点到终点的路径。回溯算法非常适合解决这类问题。

function solveMaze(maze) {
  const n = maze.length;
  const m = maze[0].length;
  const result = Array.from({ length: n }, () => Array(m).fill(0));
  
  function backtrack(x, y) {
    if (x === n - 1 && y === m - 1) {
      result[x][y] = 1;
      return true;
    }
    
    if (x >= 0 && x < n && y >= 0 && y < m && maze[x][y] === 1) {
      result[x][y] = 1;
      
      if (backtrack(x + 1, y)) {
        return true;
      }
      
      if (backtrack(x, y + 1)) {
        return true;
      }
      
      result[x][y] = 0;
      return false;
    }
    
    return false;
  }
  
  backtrack(0, 0);
  return result;
}

const maze = [
  [1, 0, 0, 0],
  [1, 1, 0, 1],
  [0, 1, 0, 0],
  [1, 1, 1, 1]
];

console.log(solveMaze(maze));

递归与回溯的优化

剪枝

剪枝是指在回溯算法中通过提前判断某些路径不可能得到解,从而减少不必要的计算。剪枝可以显著提高回溯算法的效率。

function solveNQueens(n) {
  const result = [];
  const board = Array.from({ length: n }, () => Array(n).fill('.'));
  
  function backtrack(row) {
    if (row === n) {
      result.push(board.map(row => row.join('')));
      return;
    }
    
    for (let col = 0; col < n; col++) {
      if (isSafe(row, col)) {
        board[row][col] = 'Q';
        backtrack(row + 1);
        board[row][col] = '.';
      }
    }
  }
  
  function isSafe(row, col) {
    for (let i = 0; i < row; i++) {
      if (board[i][col] === 'Q') {
        return false;
      }
    }
    
    for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
      if (board[i][j] === 'Q') {
        return false;
      }
    }
    
    for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
      if (board[i][j] === 'Q') {
        return false;
      }
    }
    
    return true;
  }
  
  backtrack(0);
  return result;
}

console.log(solveNQueens(8));

记忆化

记忆化是指在递归算法中通过保存已经计算过的结果,避免重复计算。记忆化可以显著提高递归算法的效率。

function fibonacci(n, memo = {}) {
  if (n in memo) {
    return memo[n];
  }
  if (n === 0) {
    return 0;
  }
  if (n === 1) {
    return 1;
  }
  memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
  return memo[n];
}

console.log(fibonacci(50));

尾递归优化

尾递归优化是指在递归算法中通过将递归调用放在函数的最后,从而避免栈溢出的问题。尾递归优化可以显著提高

推荐阅读:
  1. JavaScript中的递归
  2. JavaScript中递归是什么

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

javascript

上一篇:Python Flask中Cookie和Session区别是什么

下一篇:go-zero单体服务使用泛型简化注册Handler路由的问题怎么解决

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》