2019/12/06 add背包问题
2019/12/09 add排列树问题
2019/12/10 addN皇后问题
2020/01/21 add leetcode-282
references:
【算法】用回溯法(backtracking algorithm)求解N皇后问题(N-Queens puzzle)
回溯(backtracking)法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题.
X = (x1, x2, ..., xn)
需要搜索一个或一组解
满足约束条件的最优解
回溯法中,首先需要明确下面三个概念:
X = (x1, x2, ..., xn)
,X中每个分量xi所有取值的组合构成问题的解向量空间,简称解空间或者解空间树,又称为状态空间树,是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。(1)描述解的形式,定义一个解空间,它包含问题的所有解,这一步主要明确问题的解空间树。
(2)确定结点的扩展搜索规则。
(3)构造约束函数(用于杀死节点)。然后就要通过DFS思想完成回溯,DFS可以采用递归回溯或者非递归(迭代)回溯。
确定问题的解空间 --> 确定结点的扩展搜索规则–> 以DFS方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
// 递归版
const backtrack= (t)=>{
if (t>n) console.info(x); //叶子节点,输出结果,x是可行解
else{
// //当前节点的所有子节点
for (let i=0;i<k;i++){
x[t]=value(i); //每个子节点的值赋值给x
//满足约束条件和限界条件
if (constraint(t)&&bound(t))
backtrack(t+1); //递归下一层
}
}
}
//针对N叉树的迭代回溯方法
const iterativeBacktrack=()=>{
let t=1;
while (t>0) {
if(ExistSubNode(t)) //当前节点的存在子节点
{
for(let i=0;i<k;i++){
x[t]=value(i);//每个子节点的值赋值给x
if (constraint(t)&&bound(t))//满足约束条件和限界条件
{
//solution表示在节点t处得到了一个解
if (solution(t)){
console.info(x);//得到问题的一个可行解,输出
}else{
t++;//没有得到解,继续向下搜索
}
}
}
}
else //不存在子节点,返回上一层
{
t--;
}
}
}
所给的问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间成为子集树。如素数环问题,背包问题
// 算法范式
const backtrack=t=>{
if (t>n) console.info(x);
else{
for (let i=0;i<=k;i++) {
// k的大小取决于子集树的分支多少,比如素数环可以取1-n, k=n 而背包问题是0-1 k=1
x[t]=i;
if (constraint(t)){
// 满足约束条件
backtrack(t+1);
// 下面代码进行回溯
// 一般是恢复到原值即可
}
}
}
}
所给的问题是确定n个元素满足某种性质的排列时,相应的解空间就是排列树。
如旅行售货员问题,一个售货员把几个城市旅行一遍,要求走的路程最小。它的解就是几个城市的排列,解空间就是排列树。
// 算法范式
const backtrack=t=>{
if (t>n) console.info(x);
else{
for (let i=t;i<=n;i++) {
swap(x[t], x[i]);
if (constraint(t)&&bound(t)) backtrack(t+1);
swap(x[t], x[i]);
}
}
}
实际上,除了剪枝是回溯法的一个明显特征外(并非任何回溯法都包含剪枝部分),很难严格区分回溯法 与深度优先遍历。因为这些算法很多是递归算法,在递归调用中隐含着状态的自动回退和恢复。
思考三个问题:
1-n
中间的数check0
检查当前加进来的值是否和索引前有重复?是否和前一位的和为素数?若为最后一位是否和第一位的和为素数? /**
* 问题描述:输入正整数n,把整数1,2,3,…n组成一个环,使得相邻两个整数之和均为素数。输出时从整数1开始逆时针排列。同一个环应恰好输出一次。N<=16。
输入样例:6
输出样例:
1 4 3 2 5 6
1 6 5 2 3 4
素数就是质数,因数只有1和它自己。
*/
/**
* 判断一个数是不是素数,只需要折半判断即可
* @param num
* @returns {boolean}
*/
const testPrime=num=>{
let mid=Math.floor(Math.sqrt(num));
for(let i=2;i<=mid;i++){
if(num%i===0){
return false;
}
}
return true;
};
/**
* 判断当前加进去的数字是否满足条件
* 1. 是否与之前重复
* 2. 相邻之和是否为素数
* 3. 第一个和最后一个相加是否为素数
* @param a
* @param k
* @param n
* @returns {boolean}
*/
const check=(a,k,n)=>{
let flag = false ;
for (let i =0;i<k;i++) //检查是否重复
if (a[i] === a[k])
return false;
flag = testPrime(a[k]+a[k-1]); //检验与相邻之和是否是素数
if (flag ===1&&k ===n-1) //如果是最后一个,需要检查与开始值得和
flag = testPrime(a[k]+a[0]);
return flag;
};
/**
* 1:如果填入一个数是成立的,我们就继续填写下一个位置,如果这个数不成立,我们就换下一个数填写,相当于我们剪掉了这个数的树枝,去寻找下一个;
* 2:填写最后一个数时,应该考虑其应该和第一个数之和是素数;
* 使用迭代法实现:虽然代码比较多,但是思路还是比较清晰的,回溯的位置也很清晰
* @param n
*/
const primeRing=n=>{
if(n===1) return [1];
let res=[];
// initialize arr
let arr=new Array(n).fill(0);
let k=1;
arr[0]=1;
while(k>=1)
{
arr[k]=arr[k]+1;
while(arr[k]<n){
if(check(arr,k,n)){
break;
}else{
// 否则进行叠加
arr[k]=arr[k]+1;
}
}
if(arr[k]<=n&&k===n-1){
console.info(arr);
let temp=JSON.parse(JSON.stringify(arr));
res.push(temp);
}
if(arr[k]<=n&&k<n-1&&check(arr,k,n)){
k+=1;
}else{
arr[k--]=0;//剪枝回溯
}
}
return res;
};
/**
* 回溯法通常使用递归来实现,
* vis用来保存每一个1-n的元素是否在一个解向量中用过
*/
const primeRing1=n=>{
let arr=[],vis=[],res0=[];
arr[0] = 1;
vis[0]=true;
for(let i = 1; i <n;i++){
vis[i] = false;
}
/**
* check0的难点在于检测的永远是idx指针前面的数是否和k重复以及k和idx-1所在位置的数的和是否为素数
* 这也是能够回溯的关键
*/
const check0=(arr,k,idx)=>{
let flag;
for(let i=0;i<idx;i++){
if(arr[i]===k) return false;
}
flag=testPrime(arr[idx-1]+k);
return flag;
};
/**
* vis[i-1] = false;是不满足条件回溯的过程
* @param cur
*/
const dfs=cur=>{
if(cur >= n&&testPrime(arr[n]+arr[0])){
let temp=JSON.parse(JSON.stringify(arr));
res0.push(temp);
// console.info(arr);
// console.info(res0);
}else{
for(let i = 1; i <= n; i++){
// //尝试放置每个数i 使用这个代码或者下面的判断语句
// if(!vis[i-1] && testPrime(i+arr[cur-1])){//如果这个数没有被使用,并且与前一个数的和是质数
// vis[i-1] = true;//标明这个数被使用
// arr[cur] = i;//将这个数加入队列
// // console.info(arr);
// dfs(cur+1);//求下一个数
// vis[i-1] = false;//取消这个数
// }
//
if(check0(arr,i,cur)){
//如果这个数没有被使用,并且与前一个数的和是质数
arr[cur] = i;//将这个数加入队列
dfs(cur+1);//求下一个数
}
}
}
};
dfs(1);
return res0;
};
// 用子集树的思路
const primeRing=n=>{
let res=new Array(n).fill(0);
res[0]=1;
const testPrime=num=>{
let temp=Math.sqrt(num);
for(let i=2;i<=temp;i++){
if(num%i===0){
return false;
}
}
return true;
};
const check=(arr,idx,val)=>{
let flag;
// 不用forEach 避免无法跳出循环出现错误
for(let i=0;i<idx;i++){
if(arr[i]===val){
return false;
}
}
flag=testPrime(arr[idx-1]+val);
if(idx===n-1){
flag=flag&&testPrime(arr[0],val);
}
return flag;
};
const dfs=k=>{
if(k>n-1){
console.info(res);
}else{
for(let i=1;i<=n;i++){
if(check(res,k,i)){
res[k]=i;
dfs(k+1);
// 此处不必另外加回溯语句是因为每次遍历都给res[k]重新赋值了而不必再更改赋初始值 默认进入回溯状态
//res[k]=0;
}
}
}
};
dfs(1);
};
问题:给定n种物品和一背包。物品i的重量是wi,其价值为pi,背包的容量为C。问应如何选择装入背包的物品,使得装入背包中物品的总价值最大?
const knapsack=(tWeight,n,obj)=>{
let curWeight=0,curVal=0,maxVal=0,compose=new Array(n).fill(0)
,max=[];
let w=Object.keys(obj),v=[];
w.forEach(item=>{
v.push(obj[item]);
});
console.info(w,v);
const dfs=t=>{
if(t>n-1&&curWeight<=tWeight){
console.info('==>',compose,curVal);
// 控制输出条件
if(curVal>maxVal){
maxVal=curVal;
max=JSON.parse(JSON.stringify(compose));
}
}else{
for(let i=0;i<=1;i++){
if(i===0){
// 不放入背包
dfs(t+1);
}else{
// 放入背包
if(curWeight+Number(w[t])<=tWeight){
curWeight+=Number(w[t]);
curVal+=v[t];
compose[t]=w[t];
dfs(t+1);
// 回溯
compose[t]=0;
curWeight-=Number(w[t]);
curVal-=v[t];
}
}
}
}
};
dfs(0);
console.info('result==>',max,maxVal);
return maxVal;
};
/**
* n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
* 给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
* 每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
* 显然从测试用例上看符合问题的解用向量表示,需要搜索一组解的情况,因此满足用回溯法解题的要求
* 解空间自然就是子集树
* 扩展规则是n个皇后可以放在任意位置
* 约束条件是:该位置无皇后,行方向没有,列方向没有,斜方向也没有皇后
* 但是时间复杂度过高,无法AC
* 修改回溯法之后:用k参数代表所在行,
* 时间复杂度:\mathcal{O}(N!)O(N!). 放置第 1 个皇后有 N 种可能的方法,放置两个皇后的方法不超过 N (N - 2) ,放置 3 个皇后的方法不超过 N(N - 2)(N - 4) ,以此类推。总体上,时间复杂度为 \mathcal{O}(N!)O(N!) .
* 空间复杂度:\mathcal{O}(N)O(N) . 需要保存对角线和行的信息。
作者:LeetCode
链接:https://leetcode-cn.com/problems/n-queens/solution/nhuang-hou-by-leetcode/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
* @param n
*/
const solveNQueens =n=>{
let res=new Array(n),res0=[];
for(let i=0;i<n;i++){
res[i]=new Array(n).fill('.');
}
const feasible=(res0,i,j)=>{
// todo 位置不合理,已经限制过,不再处理
// todo 已经有皇后了
if(res0[i][j]==='Q'){
return false;
}
// todo 横向纵向有皇后
for(let k=0;k<n;k++){
if(res0[i][k]==='Q'||res0[k][j]==='Q'){
return false;
}
}
// todo 斜方向有皇后
// 左上角
let s=i,t=j;
while(s>=0&&t>=0){
if(res0[s][t]==='Q'){
return false;
}else{
s--;
t--;
}
}
// 右上角
s=i;t=j;
while(s<n&&t>=0){
if(res0[s][t]==='Q'){
return false;
}else{
s++;
t--;
}
}
// // 左下角
s=i;t=j;
while(s>=0&&t<n){
if(res0[s][t]==='Q'){
return false;
}else{
s--;
t++;
}
}
// // 右下角
s=i;t=j;
while(s<n&&t<n){
if(res0[s][t]==='Q'){
return false;
}else{
s++;
t++;
}
}
return true;
};
const dfs=k=>{
if(k>n-1){
res0.push(JSON.stringify(res));
}else{
// 怎么去考虑第k个皇后的摆放位置呢,显然需要双层循环,时间复杂度过高,无法AC
// 但其实我们的k可以拿来充当k,试探每一个k行的每一列
// for(let i=0;i<n;i++){
for(let j=0;j<n;j++){
if(feasible(res,k,j)){
res[k][j]='Q';
dfs(k+1);
res[k][j]='.';
}
}
// }
}
};
dfs(0);
for(let i=0;i<res0.length;i++){
res0[i]=JSON.parse(res0[i]);
for(let j=0;j<res0[i].length;j++){
res0[i][j]=res0[i][j].join('');
}
}
return res0;
};
console.info(solveNQueens(4));
/**
* 采用arr保存第i行第arr[i]列有皇后,很巧妙的方法
* @param n
* @returns {[]}
*/
const solveNQueens1=n=>{
let arr=new Array(n),res0=[],res=new Array(n);
const check=(row,col)=>{
for(let i=0;i<row;i++){
// 同一行肯定不会有的 主要检查同一列,以及斜对角,检查斜对角的方式真的很妙啊
if(col===arr[i]||Math.abs(row-i)===Math.abs(col-arr[i])){
return false;
}
}
return true;
};
const dfs=k=>{
if(k>n-1){
arr.forEach((item,idx)=>{
res[idx]=new Array(n).fill('.');
res[idx][item]='Q';
res[idx]=res[idx].join('');
});
res0.push(JSON.parse(JSON.stringify(res)));
}else{
// 试探每一个k行的每一列
// for(let i=0;i<n;i++){
for(let j=0;j<n;j++){
if(check(k,j)){
arr[k]=j;
dfs(k+1);
// 此处不必另外加回溯语句是因为每次遍历都给arr[k]重新赋值了而不必再更改赋初始值 参考primeRing同理
}
}
// }
}
};
dfs(0);
return res0;
};
以下是两种方法,优化前的暴力遍历法和优化后的方法
/*
* @param {string} num
* @param {number} target
* @return {string[]}
*/
const addOperators = (num, target)=>{
let res=[],ans=[];
const findRes=(num0,k)=>{
if(num0.length<1) return;
let flag=(num0.length>1&&num0[0]!=='0')||(num0.length===1);
if(flag&&eval(`${
res.slice(0,k).join('')}${
num0}`)===target){
res[k]=num0;
ans.push(res.slice(0,k+1).join(''));
}else{
for(let i=0;i<num0.length;i++){
res[k]=num0.slice(0, i + 1);
res[k+1]='+';
findRes(num0.slice(i + 1), k+2);
res[k+1]='-';
findRes(num0.slice(i + 1), k+2);
res[k+1]='*';
findRes(num0.slice(i + 1),k+2);
if(num0[0]==='0') break;
}
}
};
findRes(num,0);
return ans;
};
======> 优化
1-2*3*4*5
这种情况,其若从1-2*3*4
过渡需要:1-2*3*4-(-2*3*4)+(-2*3*4*5)
/**
* @param num
* @param target
* @returns {number}
*/
const addOperators2 = (num, target)=>{
let res=[],ans=[];
const backTrack=(num0,val,pre,k)=>{
if(num0.length<1){
if(val===target){
ans.push(res.slice(0,k).join(''));
}
return;
}
for(let i=0;i<num0.length;i++){
if(k===0){
res[k]=(num0.slice(0,i+1));
backTrack(num0.slice(i+1),Number(res[k]),Number(res[k]),k+1);
}else{
res[k]=('+');
res[k+1]=num0.slice(0,i+1);
backTrack(num0.slice(i+1),val+Number(res[k+1]),Number(res[k+1]),k+2);
res[k]=('-');
res[k+1]=num0.slice(0,i+1);
backTrack(num0.slice(i+1),val-Number(res[k+1]),-Number(res[k+1]),k+2);
res[k]=('*');
res[k+1]=num0.slice(0,i+1);
backTrack(num0.slice(i+1),val-pre+pre*Number(res[k+1]),pre*Number(res[k+1]),k+2);
}
if(num0[0]==='0') break;
}
};
backTrack(num,0,0,0);
return ans;
};
目录Nacos介绍什么是 Nacos?Nacos 地图Nacos 生态图Nacos 概念地域可用区接入点命名空间配置配置管理配置项配置集配置集 ID配置分组配置快照服务服务名服务注册中心服务发现元信息应用服务分组虚拟集群实例权重健康检查健康保护阈值Nacos 架构基本架构及概念服务 (Service)服务注册中心 (Service Registry)服务元数据 (Servic.
给定正整数n和m,计算出n个元素的集合{1,2,...,n}可以划分为多少个不同的由m个元素组成的子集合shou'xia
感觉学习的东西慢慢的积累中,希望能够将自己所学的东西分享出来。对有需要的程序员们提供参考,同时积累知识以便以后回顾复习。
http://www.cooco.net.cn/ 抽题系统家居 装饰品 化妆品http://www.jb51.net/脚本之家http://www.threadlesskids.com/分类:其他,C# Asp.net本文转自快乐就好博客园博客,原文链接:http://www.cnblogs.com/happyday56/a...
层次聚类(hierarchical clustering)可在不同层次上对数据集进行划分,形成树状的聚类结构。AggregativeClustering是一种常用的层次聚类算法。 其原理是:最初将每个对象看成一个簇,然后将这些簇根据某种规则被一步步合并,就这样不断合并直到达到预设的簇类个数。这里的关键在于:如何计算聚类簇之间的距离? 由于每个簇就是一个集合,因此需要给出集合之间的距离。给
解决办法:pip install numba==0.53
我是技术搬运工,好东西当然要和大家分享啦.原文地址概览容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。1. ListArrayList:基于动态数组实现,支持随机访问;LinkedList:基于双向循环链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双端队列...
前段时间,公司几年前开发的站点受到了攻击,不是SQL注入的攻击,而是aspxspy的攻击,这是一个ASP的页面,以运行CmdShell的方式来获取服务器的信息,但是以后缀名为jpg的文件保存,将其伪装成图片上传至服务器,然后就开始读取服务器上的所有信息,如果在开发的时候就对服务器上各个文件夹的权限做了详细的设置的话,这个程序倒是没有权限去更改服务器上的文件,但是发生这种情况最根本的源头就是未对上传...
文章所有用到的文件都在这个压缩包里链接:https://pan.baidu.com/s/1ib7pUGtEDp_DqsuO5jOrAA 密码:vvtx首先本文参照Hek_watermelon的博客编写,解决了部署中遇到的一些问题,传送门https://blog.csdn.net/hekanhyde/article/details/78595236下面开始安装dockerCento...
DBCP,C3P0,Proxool,BoneCP详细参数介绍 BoneCP:acquireIncrement: 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3driveClass:数据库驱动 jdbcUrl:响应驱动的jdbcUrl username:数据库的用户名 password:数据库的密码 idleConnectionT
第一周:深度学习的实用层面 本周主要讲解神经网络机器学习中的问题,学习一些能够确保神经网络正确运行的技巧。例如在配置训练、验证和测试集的过程中做出正确的决策会在很大程度上帮助大家创建高效的神经网络。训练神经网络时,我们常需要做出很多决策,例如神经网络分多少层;每层含有多少个隐藏单元;学习速率是多少;各层采取哪些激活函数等。1.1 训练、开发、测试集 深度学...
理科学习中要是有一些工具能够帮助我们,那我们的学习生活将会有如神助!学习效率将会大大提高!有了这些学习工具,数学物理化学这些个老大难的问题,都可以被我们攻克了。下面罗列了一些学习中的工具,从中学阶段到研究阶段都可用上。一、通用篇MathType数学公式编辑器MathType是一款专门文档中编辑数学公式的编辑器,里面所含有的各种数学物理符号可以满足我们平常的使用需要。在中学阶段,老师们使...