回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法(深度优先),按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
采取三段式:(3个if)
例如:0-1背包问题
//到达叶节点(退出条件)
if(i>n){
}
//进入左子树
if(cw+wi<=c){
}
//进入右子树
if(bound(i+1)>best){
}
//减枝函数
public void bound()
{
}
采取两段式:(if+for)
例如:旅行售货员问题
//到达叶节点
if(i>n){
}
else{
//通过for循环遍历排列树
for(j=t; j<=n; j++){
if(...)
{
Swap(x[i],x[j]);
}
}
}
一批集装箱共n个要装上2艘载重量分别为c1和c2的轮船,其中集装箱i的重量为Wi且W1+W2+…+Wn<=c1+c2;试确定一个合理的装载方案使这n个集装箱装上这两艘轮船。
容易去证明:如果一个装载问题有解,则采用下面的策略可以得到最优装载方案:
(1)首先将第一艘轮船尽可能装满;
(2)然后将剩余的集装箱装在第二艘轮船上。
那么在这个过程中,我们需要找到尽可能把第一个轮船装满的解。如果用回溯法解决问题,我们可以首先分析问题得解空间结构,应该是一个子集树。然后我们可以把问题套入相应的模版进行解决。
上界函数:在以Z为根的子树中任意叶节点所相应的载重量均不超过CW+R,因此当BESTW>=CW+R时,可减去右子树。
遍历完全二叉树所需的时间为(2^n),记录最优解需要(n)
所以算法复杂度为(n2^n)
每一个作业Ji都有两项任务分别在2台机器上完成。每个作业必须先有机器1处理,然后再由机器2处理。作业Ji需要机器j的处理时间为tji。对于一个确定的作业调度,设Fji是作业i在机器j上完成处理时间。则所有作业在机器2上完成处理时间和f=F2i,称为该作业调度的完成时间和
对于给定的n个作业,指定最佳作业调度方案,使其完成时间和达到最小。
区别于流水线调度问题:批处理作业调度旨在求出使其完成时间和达到最小的最佳调度序列;
流水线调度问题旨在求出使其最后一个作业的完成时间最小的最佳调度序列;
按照回溯法搜索排列树的算法框架,设开始时x=[1,2, … , n]是所给的n个作业,则相应的排列树由x[1:n]的所有排列(所有的调度序列)构成。
二维数组M是输入作业的处理时间,bestf记录当前最小完成时间和,bestx记录相应的当前最佳作业调度。
在递归函数Backtrack中,
当i>n时,算法搜索至叶子结点,得到一个新的作业调度方案。此时算法适时更新当前最优值和相应的当前最佳调度。
当i 1、区分作业i和当前第i个正在执行的作业 遍历排列树所需的时间为(n!) 问题描述: 由14个“+”号和14个“-”号组成的符号三角形。 2个同号下面是“+”号,2个异号下面是“-”号。 如图: + + _ + _ + + + _ _ _ _ + _ + + + _ _ + + _ _ + _ _ _ + 在一般情况下,符号三角形第一行有N个符号,该问题要求对于给定n计算有多少种不同的符号三角形。使其所含的 + 和 - 个数相同。 1 x[i] =1 时,符号三角形的第一行的第i个符号为+ 2 x[i] =0时,表示符号三角形的第一行的第i个符号位- 共有i(i+1)/2个符号组成的符号三角形。 3 确定x[i+1]的值后,只要在前面确定的符号三角形的右边加一条边就扩展为x[1:i+1]所相应的符号三角形。 4 最后三角形中包含的“+”“-”的个数都为i(i+1)/4,因此搜索时,个数不能超过…若超直接可以剪去分枝。 5 当给定的n(n+1)/2为奇数时,也不符合三角形要求。 遍历完全二叉树所需的时间为(2^n),记录最优解需要(n) 八皇后问题,是一个古老而著名的问题.该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法? 遍历排列树所需的时间为(n!) 某售货员要到若干城市去推销商品,已知各城市之间的路程,他要选定一条从驻地出发,经过每个城市一遍,最后回到住地的路线,使总的路程最短。 旅行售货员问题的解空间是一颗排序树,对于排序树的回溯法搜索与生成1,2,3,4,…,n的所有排列的递归算法Perm类似,开始时,x = [1,2,…,n],则相应的排序树由x[1:n]的所有排序构成。以下解释排序算法Perm: (1)假设Perm(1)的含义是对x = [1,2,…,n]进行排序,则Perm(i)的含义是对[i,i+1,i+2,…,n]进行排序。为了方便描述,规定一个操作P(j),表示在数组x中,第j个与第一个交换,于是得到递归关系式: Perm(i) = P(i)Perm(i+1) + P(i+1)Perm(i+1) + …+P(n)Perm(i+1) (2)旅行售货员的回溯法Backtrack在Perm的基础上使用了剪枝函数,将无效的全排列和有效的全排列非最优的次序统统都舍去。 遍历排列树所需的时间为((n-1)!),更新BESTX(n) 遍历完全二叉树所需的时间为(2^n),记录最优解需要(n)4、前期准备
给x赋初值,即其中一种排列,如x=[1,3,2];M[x[j]][i]代表当前作业调度x排列中的第j个作业在第i台机器上的处理时间;如M[x[2]][1]就意味着作业3在机器1上的处理时间。
2、bestf的初值
此问题是得到最佳作业调度方案以便使其完成时间和达到最小,所以当前最优值bestf应该初始化赋值为较大的一个值。
3、f1、f2的定义与计算
假定当前作业调度排列为:x=[1,2,3];f1[i]即第i个作业在机器1上的处理时间,f2[j]即第j个作业在机器2上的处理时间;则:
f1[1]=M[1][1] , f2[1]=f1[1]+M[1][2]
f1[2]=f1[1]+M[2][1] , f2[2]=MAX(f2[1],f1[2])+M[2][2] //f2[2]不光要等作业2自己在机器1上的处理时间,还要等作业1在机器2上的处理时间,选其大者。
f1[3]=f1[2]+M[3][1] , f2[3]=MAX(f2[2],f1[3])+M[3][2]
1只有当前值有用,可以覆盖赋值,所以定义为int型变量即可,减少空间消耗;f2需要记录每个作业的处理时间,所以定义为int *型,以便计算得完成时间和。
4、f2[0]的初值
f2[i]的计算都是基于上一个作业f2[i-1]进行的,所以要记得给f2[0]赋值为0。
#include
3、时间复杂性
所以算法复杂度为(n!)三、符号三角形问题
1.问题描述
2.算法分析
3.时间复杂性
所以算法复杂度为(n2^n)四、N后问题
1.问题描述:
2.算法设计
由题易知:每一行必有放置一个皇后。
开一个二维数组来保存皇后,则皇后的坐标必为[1][x].[2][x].[3][x].[4][x]…
横坐标是固定的,只需要确定皇后的列即可。
经过分析,N后问题为一个排列问题。
只需判断对角线是否冲突即可。待写
3.时间复杂性
计算可行性约束需要(n)
所以算法复杂度为(n*n!)五、旅行售货员问题
1.问题描述
2.算法分析
void Traveling::Backtrack(int i) //对数组x中第i起到结尾进行全排列的试探,数组x下标为0的元素保留不用
{
if(i == n) //找到符合条件的全排列
{
if (a[x[i-1]][x[i]] != NoEdge
&& a[x[i]][x[1]] != NoEdge
&& (bestc > cc + a[x[i-1]][x[i]] +a[x[i]][x[1]] || bestc == NoEdge)) //判断是否有回路、发现最优值
{
bestc = cc + a[x[i-1]][x[i]] +a[x[i]][x[1]]; //保存最优值
for (int i = 1; i <= n; i++)
{
bestx[i] = x[i]; //保存最优解
}
}
}
else
{
for (int j =i; j <= n; j++)
{
if(a[x[i-1]][x[j]] != NoEdge && (cc + a[x[i-1]][x[j]] < bestc || bestc == NoEdge))
{
Swap(x[i],x[j]);
cc += a[x[i-1]][x[i]];
Backtrack(i+1);
cc -= a[x[i-1]][x[i]];
Swap(x[i],x[j]);
}
}
}
}
3.时间复杂性
所以算法复杂度为(n!)六、0-1背包问题
public void backtrack(int i)
{
if (i>n-1)
{
if(cp>bestp)
{
bestp = cp;
for(int x=0;x
3.时间复杂性
所以算法复杂度为(n2^n)