浅谈--回溯法

439 阅读2分钟

回溯法理解

回溯法等于 递归 + for循环

其实也就是两个维度上的递归 是不是想到了坐标系,树状图。是的。

边界函数 顾名思义,就是终止函数,对深度上的限制

剪支函数 顾名思义,就是某些不符合搜索条件的子分子不再进行递归,广度上的限制

边界函数 剪支函数 用于缩小搜索范围

回溯法一定要从广度深度去理解

image.png

横向是广度:集合大小约束,纵向是深度:边界函数 约束

剪支函数就是附加条件

粗暴搜索,深度优先遍历Deep First Search(后面会解释)

递归

递归 等于 递归条件 + 调用自己

var list = [2,4,56,32];
function sum(list){
  if(list.length>0){ // 递归条件
    return list.pop() + sum(list); // 调用自己
  }else{
    return 0;
  }
}

var a = sum(list);

32+(56+(4+2)); 反向执行

回溯法设计代码

for 循环 + 递归

解决n次for循环嵌套问题

比如: 组合问题,1,2,3,4,找出所有2位组合

const path2=[]
for (var i=1; i<=4;i++){
  for(var j=i+1;j<=4;j++){ // 嵌套2次
    path2.push(i+'.'+j)
  }
}
console.log(path2)
// ["1.2", "1.3", "1.4", "2.3", "2.4", "3.4"]

组合问题,1,2,3,4,找出所有3位组合

const path2=[]
let nums = 0;
for (var i=1; i<=4;i++){
  nums++
  for(var j=i+1;j<=4;j++){ // 嵌套2次
      nums++
      for(var k=j+1;k<=4;k++){ // 嵌套3次
       //.
       //.
       //.
       // 无尽套娃地狱
       nums++
        path2.push(i+'.'+j+'.'+k)
      }
  }
}
console.log(path2,nums) // nums:14
// ["1.2.3", "1.2.4", "1.3.4", "2.3.4"]

所以: 组合的大小是: for循环的宽度,目标结果的大小是: 递归的深度

循环的过程就是 从宽度的 1 -> 4,递归 for循环查找子节点,直到达到目标深度

先输出1的所有结果就是 深度优先遍历

设计回溯代码

  1. 画出状态树

image.png

  1. 遍历 穷举法遍历,就是高中学的排列组合法

  2. 写代码

最后要做的就是转换成程序代码啦!

第一步,确定参数,一般根据题目要求来确定

1,2,3,4找出所有2位组合

参数一,数据长度n=4;参数二,目标长度k=2;参数三,回溯初始值 inite=1; results = []目标数组

var results = [];
function backtracking(n,k,inite){

    .
    .
    .
    return results;
}

第二步,确定结束条件
var results = [];
var path = [];
function backtracking(n,k,inite){

    if(path.length == k){//结束条件
        results.push(path.join(','));
        return;
    }
    .
    .
    .
    return results;
}

第三步,for 循环;
var results = [];
var path = [];
function backtracking(n,k,inite){

    if(path.length == k){
        results.push(path.join(','));
        return;
    }
    for (var a=inite;a <= n;a++){ //for 循环;
        path.push(a);
    }
    return results;
}

第四步,递归;
var results = [];
var path = [];
function backtracking(n,k,inite){
    if(path.length == k){
        results.push(path.join(','));
        return;
    }
    for (var a=inite;a <= n;a++){ 
        backtracking(n,k,a+1);//递归;
    }
}
backtracking(4,2,1)
console.log(results)
第五步,回溯;
var results = [];
var path = [];
function backtracking(n,k,inite){
    if(path.length == k){
        results.push(path.join(','));
        return;
    }
    for (var a=inite;a <= n;a++){ 
        path.push(a);//回溯;
        backtracking(n,k,a+1);
        path.pop(a);//回溯;
    }
}
backtracking(4,2,1)
console.log(results)
// ["1.2", "1.3", "1.4", "2.3", "2.4", "3.4"]

终结

for 循环是宽度上的迭代

backtracking(n,k,a+1); a+1是深度上的迭代

Leetcode练习题

题目数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:

输入:n = 1
输出:["()"]
var results=[];
var str = [];
// 第一步确定参数 n是输入的目标括号对数 3对;
//left 是“(” 的剩余数目,取一次递减;right是“)”的剩余数目,取一次递减
var generateParenthesis = function(n,left,right) { 
  // 终止条件 “(” 和 “)”的剩余数目都为0;
  // 边界函数
  if(left == 0 && right==0){
   results.push(str.join(''));
   return;
  }
  // left还有的时候就可以取“(”
  if(left>0){ // 剪支函数
    str.push('(');
    generateParenthesis(n,left-1,right);
    str.pop();
  }
  // 只有当right 的 数目大于left才能取 “)”
  if(left<right){ // 剪支函数
    str.push(')');
    generateParenthesis(n,left,right-1);
    str.pop();
  }
};
// n=3,left初始值3,right初始值3;
generateParenthesis(3,3,3)
console.log(results) //  ["((()))", "(()())", "(())()", "()(())", "()()()"]

八皇后问题

八皇后的难点在于

存放结果的path:是个二维数组

剪支函数:isSafe,稍微复杂一点

function getQueens(n){
  var results = [];
  //path是个二维数组
  var path = Array(n).fill('.');
  path = path.map(i => Array(n).fill('.'));
  //剪支函数
  function isSafe(row,col,n){
    // row 列,col行
    for(var j=0;j<n;j++){
      for(var i=0;i<col;i++){
        if(path[i][j]=='Q' && (row==j || row+col==j+i || row-col == j-i)){
          return false;
	}
      }
    }
    return true;
  }
  function backTracking(n,height){
    if(height == n){
      var rel = []
      path.map(i => {
        rel.push(i.join(''))
      })
      results.push(rel)
      return;
    }
    for(var a =0;a<n;a++){
      if(isSafe(a,height,n)){
        path[height][a] = 'Q';
        backTracking(n,height+1);
        path[height][a] = '.';
      }
    }
  }
  backTracking(n,0)
  return results;
}
console.log(getQueens(8))