回溯法理解
回溯法等于 递归 + for循环
其实也就是两个维度上的递归
是不是想到了坐标系,树状图。是的。
边界函数
顾名思义,就是终止函数,对深度上的限制
剪支函数
顾名思义,就是某些不符合搜索条件的子分子不再进行递归,广度上的限制
边界函数
剪支函数
用于缩小搜索范围
回溯法一定要从广度和深度去理解
横向是广度:集合大小
约束,纵向是深度:边界函数
约束
剪支函数
就是附加条件
粗暴搜索,深度优先遍历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,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))