文章目录
回溯法
1.概述
回溯(backtracking)法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
回溯法是一个既带有系统性又带有跳跃性的的搜索算法。它在包含问题的所有解的解空间树中,按照深度优先的策略,从根结点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则,进入该子树,继续按深度优先的策略进行搜索。回溯法在用来求问题的所有解时,要回溯到根,且根结点的所有子树都已被搜索遍才结束。而回溯法在用来求问题的任一解时,只要搜索到问题的一个解就可以结束。这种以深度优先的方式系统地搜索问题的解的算法称为回溯法,它适用于解一些组合数较大的问题.
适用条件
问题的解用向量表示 X = (x1, x2, …, xn)
需要搜索一个或一组解 满足约束条件的最优解
2.三个概念
**约束函数:**约束函数是根据题意定出的。通过描述合法解的一般特征用于去除不合法的解,从而避免继续搜索出这个不合法解的剩余部分。因此,约束函数是对于任何状态空间树上的节点都有效、等价的。
**状态空间树:**一个问题的解可以表示成解向量X = (x1, x2, …, xn),X中每个分量xi所有取值的组合构成问题的解向量空间,简称解空间或者解空间树,又称为状态空间树,是一个对所有解的图形描述。树上的每个子节点的解都只有一个部分与父节点不同。
**扩展节点、活结点、死结点:**所谓扩展节点,就是当前正在求出它的子节点的节点,在DFS中,只允许有一个扩展节点。活结点就是通过与约束函数的对照,节点本身和其父节点均满足约束函数要求的节点;死结点反之。由此很容易知道死结点是不必求出其子节点的(没有意义)。
由于采用回溯法求解时存在退回到祖先结点的过程,所以需要保存搜索过的结点。通常采用: 定义栈来保存 采用递归方法
用回溯法通常采用两种策略(均称为剪枝函数)避免无效搜索。 用约束函数在扩展结点处剪除不满足约束条件的路径
用限界函数剪去得不到问题的解或最优解的路径
3.解题步骤
(1)描述解的形式,定义一个解空间,它包含问题的所有解,这一步主要明确问题的解空间树。
(2)确定结点的扩展搜索规则。
(3)构造约束函数(用于杀死节点)。然后就要通过DFS思想完成回溯,DFS可以采用递归回溯或者非递归(迭代)回溯。 确定问题的解空间 -->
确定结点的扩展搜索规则–> 以DFS方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
一:符号三角形问题
1.问题描述
符号三角问题:下面都是“-”。 下图是由14个“+”和14个“-”组成的符号三角形。2个同号下面都是“+”,2个异号下面都是“-”。
在一般情况下,符号三角形的第一行有n个符号。符号三角形问题要求对于给定的n,计算有多少个不同的符号三角形,使其所含的“+”和“-”的个数相同。
参考代码如下,请在此基础上,实现如下功能:
- 分别输出n的值为1----20时,对应的符号三角形个数,如没有满足条件的符号三角形,则输出0
- 输入一个整数n,输出对应的符号三角形的个数,并依次显示出所有的符号三角形。
2.解题思路
(1)解题思路:
1、不断改变第一行的每个符号,搜索符合条件的解,可以使用递归回溯。
为了便于运算,设+ 为0,- 为1,这样可以使用异或运算符表示符号三角形的关系:
++为+即0^0=0, --为+即1^1=0, ±为-即0^1=1, -+为-即1^0=1
2、因为两种符号个数相同,可以对题解树剪枝
当所有符号总数为奇数时无解,当某种符号超过总数一半时无解
(2)算法设计
在符号三角形的第一行的前i个符号x[1:i]确定后,就确定了一个由i*(i+1)/2个符号组成的符号三角形。
下一步确定了x[1:i]的值后,只要在前面确定的符号三角形的右边加一条边,就可以扩展为x[1:i]所对应的符号三角形。
在回溯法搜索过程中可用当前符号三角形所包含的“+”号个数与“-”号个数均不超过n*(n-1)/4作为可行性约束,用于剪去不满足约束条件的子树。
3.程序代码
public class Triangles{
static int n, //第一行符号的个数
count, //n*(n+1)/2
half; //当前"+"的个数
static int [][] p; //符号三角形矩阵
static long sum; // 已找到的符号三角形数
public static long compute(int nn)
{
n=nn;
count=0;
sum=0;
half=n*(n+1)/2;
if (half%2==1) return 0;
half=half/2;
p=new int[n+1][n+1];
for(int i=0;i<=n;i++)
for(int j=0; j<=n;j++)
p[i][j]=0;
backtrack(1);
return sum;
}
public static void backtrack (int t)
{
if((count>half)||(t*(t-1)/2-count>half))
{
return; //剪去不满足约束的子树
}
if(t>n)
{
sum++; //找到一个满足要求的三角形
/*
可在此处加入代码,输出当前解对应的符号三角形
*/
}
else
for(int i=0;i<2;i++)
{
//子树
p[1][t]=i;
count+=i;
for(int j=2;j<=t;j++) //该子树形成的三角形
{
p[j][t-j+1]=p[j-1][t-j+1]^p[j-1][t-j+2];
count+=p[j][t-j+1];
}
backtrack(t+1);
for(int j=2;j<=t;j++) //回溯恢复
count-=p[j][t-j+1];
count-=i;
}
}
public static void main(String[] args) {
int n=4; long result;
result=compute(n);
System.out.println(String.format("result=%d",result));
}
}
package suanfa;
public class suanfa1 {
static int n;//第一行的符号个数
static int half;//n*(n+1)/4
static int count;//当前“+”或者“-”的个数
static int[][] p;//符号三角形矩阵
static