最佳调度问题 回溯算法分析
算法设计与分析——批处理作业调度(回溯法)
算法设计与分析——批处理作业调度(回溯法)之前讲过⼀个相似的问题流⽔作业调度问题,那⼀道题最开始⽤动态规划,推到最后得到了⼀个Johnson法则,变成了⼀个排序问题,有兴趣的可以看⼀下本篇博客主要参考⾃⼀、问题描述给定n个作业的集合{J1,J2,…,Jn}。
每个作业必须先由机器1处理,然后由机器2处理。
作业Ji需要机器j的处理时间为t ji。
对于⼀个确定的作业调度,设Fji是作业i在机器j上完成处理的时间。
所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和。
批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度⽅案,使其完成时间和达到最⼩。
例:设n=3,考虑以下实例:看到这⾥可能会对这些完成时间和是怎么计算出来的会有疑问,这⾥我拿123和312的⽅案来说明⼀下。
对于调度⽅案(1,2,3)作业1在机器1上完成的时间是2,在机器2上完成的时间是3作业2在机器1上完成的时间是5,在机器2上完成的时间是6作业3在机器1上完成的时间是7,在机器2上完成的时间是10所以,作业调度的完成时间和= 3 + 6 + 10这⾥我们可以思考⼀下作业i在机器2上完成的时间应该怎么去求?作业i在机器1上完成的时间是连续的,所以是直接累加就可以。
但对于机器2就会产⽣两种情况,这两种情况其实就是上图的两种情况,对于(1,2,3)的调度⽅案,在求作业2在机器2上完成的时间时,由于作业2在机器1上还没有完成,这就需要先等待机器1处理完;⽽对于(3,1,2)的调度⽅案,在求作业2在机器2上完成的时间时,作业2在机器1早已完成,⽆需等待,直接在作业1被机器1处理之后就能接着被处理。
综上,我们可以得到如下表达式if(F2[i-1] > F1[i])F2[i] = F2[i-1] + t[2][i]elseF2[i] = F1[i] + t[2][i]⼆、算法设计类Flowshop的数据成员记录解空间的结点信息,M输⼊作业时间,bestf记录当前最⼩完成时间和,数组bestx记录相应的当前最佳作业调度。
回溯算法详解
回溯算法详解
回溯算法是一种经典问题求解方法,通常被应用于在候选解的搜索空间中,通过深度优先搜索的方式找到所有可行解的问题。
回溯算法的本质是对一棵树的深度优先遍历,因此也被称为树形搜索算法。
回溯算法的基本思想是逐步构建候选解,并试图将其扩展为一个完整的解。
当无法继续扩展解时,则回溯到上一步并尝试其他的扩展,直到找到所有可行的解为止。
在回溯算法中,通常会维护一个状态向量,用于记录当前已经构建的解的情况。
通常情况下,状态向量的长度等于问题的规模。
在搜索过程中,我们尝试在状态向量中改变一个或多个元素,并检查修改后的状态是否合法。
如果合法,则继续搜索;如果不合法,则放弃当前修改并回溯到上一步。
在实际应用中,回溯算法通常用来解决以下类型的问题:
1. 组合问题:从n个元素中选取k个元素的所有组合;
2. 排列问题:从n个元素中选择k个元素,并按照一定顺序排列的所有可能;
3. 子集问题:从n个元素中选择所有可能的子集;
4. 棋盘问题:在一个给定的n x n棋盘上放置n个皇后,并满足彼此之间不会互相攻击的要求。
回溯算法的时间复杂度取决于候选解的规模以及搜索空间中的剪枝效果。
在最坏情况下,回溯算法的时间复杂度与候选解的数量成指数级增长,因此通常会使用剪枝算法来尽可能减少搜索空间的规模,从而提高算法的效率。
总之,回溯算法是一种非常有用的问题求解方法,在实际应用中被广泛使用。
同时,由于其时间复杂度较高,对于大规模的问题,需要慎重考虑是否使用回溯算法以及如何优化算法。
回溯算法原理和几个常用的算法实例
回溯算法原理和几个常用的算法实例回溯算法是一种基于深度优先的算法,用于解决在一组可能的解中找到满足特定条件的解的问题。
其核心思想是按照特定的顺序逐步构造解空间,并通过剪枝策略来避免不必要的。
回溯算法的实现通常通过递归函数来进行,每次递归都尝试一种可能的选择,并在达到目标条件或无法继续时进行回溯。
下面介绍几个常用的回溯算法实例:1.八皇后问题:八皇后问题是一个经典的回溯问题,要求在一个8×8的棋盘上放置8个皇后,使得每个皇后都不能相互攻击。
即每行、每列和对角线上都不能有两个皇后。
通过在每一列中逐行选择合适的位置,并进行剪枝,可以找到所有满足条件的解。
2.0-1背包问题:0-1背包问题是一个经典的组合优化问题,要求在一组物品中选择一些物品放入背包,使得其总重量不超过背包容量,同时价值最大化。
该问题可以通过回溯算法进行求解,每次选择放入或不放入当前物品,并根据剩余物品和背包容量进行递归。
3.数独问题:数独问题是一个经典的逻辑推理问题,要求在一个9×9的网格中填入数字1-9,使得每行、每列和每个3×3的子网格中都没有重复数字。
该问题可以通过回溯算法进行求解,每次选择一个空格,并依次尝试1-9的数字,然后递归地进行。
4.字符串的全排列:给定一个字符串,要求输出其所有可能的排列。
例如,对于字符串"abc",其所有可能的排列为"abc"、"acb"、"bac"、"bca"、"cab"和"cba"。
可以通过回溯算法进行求解,每次选择一个字符,并递归地求解剩余字符的全排列。
回溯算法的时间复杂度通常比较高,因为其需要遍历所有可能的解空间。
但是通过合理的剪枝策略,可以减少的次数,提高算法效率。
在实际应用中,可以根据具体问题的特点来设计合适的剪枝策略,从而降低算法的时间复杂度。
最佳调度问题(搜索回溯)
最佳调度问题(搜索回溯)最佳调度问题【问题描述】假设有n个任务由k个可并⾏⼯作的机器完成。
完成任务i需要的时间为ti。
试设计⼀个算法找出完成这n个任务的最佳调度,使得完成全部任务的时间最早。
【编程任务】对任意给定的整数n和k,以及完成任务i需要的时间为ti,i=1~n。
编程计算完成这n个任务的最佳调度。
【输⼊格式】由⽂件machine.in给出输⼊数据。
第⼀⾏有2 个正整数n和k。
第2 ⾏的n个正整数是完成n个任务需要的时间。
【输出格式】将计算出的完成全部任务的最早时间输出到⽂件machine.out。
【输⼊样例】7 32 14 4 16 6 5 3【输出样例】17/*搜索问题我会先想每个⼩问题⾯临的共同条件是什么,每加⼊⼀件任务,他加⼊的条件是什么?他要加⼊哪个机器?加⼊的条件是什么?时间怎样最短?然后我就蒙圈了、、、我智障的想法是search(1,1)把第⼀个加⼊第⼀个机器,然后直到search(7,1)把第七个加⼊第⼀个,然后再加⼀个机器search(1,2)直到search(7,2)后来想想真是愚蠢呐,上⽹搜了⼀下、、才明⽩...orz,search(int,int)⾥⾯的两个参数⼀个表⽰加⼊的第⼏个任务,另⼀个是现在的时间,所以search()⾥⾯是什么很重要,我发现这就⽐如前⾯的题⽬的效率问题,求最⾼的效率,search()⾥⾯也有⼀个参数代表效率,从0开始;还有着⼀个题注意剪枝,当这⼀种情况已经⽐之前找到的最⼩值⼤时这种情况就可以舍弃不⽤考虑了QWQ*/#include<iostream>#include<cstdio>#include<cstdlib>using namespace std;int a[30],s[30];int n,k,minn=0x7ffff;void search(int,int);int main() {scanf("%d%d",&n,&k);for(int i=1; i<=n; i++) //输⼊每个时间scanf("%d",&a[i]);search(1,0);//加⼊第⼀个时间,现在花费的时间是0;cout<<minn;return0;}void search(int x,int y) {if(y>=minn)return;//剪枝已经⽐最⼩的⼤了就不⽤再放了,舍弃这种⽅法if(x>n) { //放完了进⾏判断if(y<minn)minn=y;return;}for(int i=1; i<=k; i++) {if(s[i]+a[x]<=minn) { //剪枝s[i]+=a[x];search(x+1,max(y,s[i]));s[i]-=a[x];//回溯}}return;}。
0028算法笔记——【回溯法】批作业调度问题和符号三角形问题
1、批作业调度问题(1)问题描述给定n个作业的集合{J1,J2,…,Jn}。
每个作业必须先由机器1处理,然后由机器2处理。
作业Ji需要机器j的处理时间为tji。
对于一个确定的作业调度,设Fji是作业i在机器j上完成处理的时间。
所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和。
批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度方案,使其完成时间和达到最小。
例:设n=3,考虑以下实例:这3个作业的6种可能的调度方案是1,2,3;1,3,2;2,1,3;2,3,1;3,1,2;3,2,1;它们所相应的完成时间和分别是19,18,20,21,19,19。
易见,最佳调度方案是1,3,2,其完成时间和为18。
(2)算法设计批处理作业调度问题要从n个作业的所有排列中找出具有最小完成时间和的作业调度,所以如图,批处理作业调度问题的解空间是一颗排列树。
按照回溯法搜索排列树的算法框架,设开始时x=[1,2,……n]是所给的n个作业,则相应的排列树由x[1:n]的所有排列构成。
算法具体代码如下:[cpp]view plain copy1.#include "stdafx.h"2.#include <iostream>ing namespace std;4.5.class Flowshop6.{7.friend int Flow(int **M,int n,int bestx[]);8.private:9.void Backtrack(int i);10.11.int **M, // 各作业所需的处理时间12. *x, // 当前作业调度13. *bestx, // 当前最优作业调度14.15. *f2, // 机器2完成处理时间16. f1, // 机器1完成处理时间17. f, // 完成时间和18.19. bestf, // 当前最优值20. n; // 作业数21. };22.23.int Flow(int **M,int n,int bestx[]);24.25.template <class Type>26.inline void Swap(Type &a, Type &b);27.28.int main()29.{30.int n=3,bf;31.int M1[4][3]={{0,0,0},{0,2,1},{0,3,1},{0,2,3}};32.33.int **M=new int*[n+1];34.35.for(int i=0;i<=n;i++)36. {37. M[i]=new int[3];38. }39. cout<<"M(i,j)值如下:"<<endl;40.41.for(int i=0;i<=n;i++)42. {43.for(int j=0;j<3;j++)44. {45. M[i][j]=M1[i][j];46. }47. }48.49.int bestx[4];50.for(int i=1;i<=n;i++)51. {52. cout<<"(";53.for(int j=1;j<3;j++)54. cout<<M[i][j]<<" ";55. cout<<")";56. }57.58. bf=Flow(M,n,bestx);59.60.for(int i=0;i<=n;i++)61. {62.delete []M[i];63. }64.delete []M;65.66. M=0;67.68. cout<<endl<<"最优值是:"<<bf<<endl;69. cout<<"最优调度是:";70.71.for(int i=1;i<=n;i++)72. {73. cout<<bestx[i]<<" ";74. }75. cout<<endl;76.return 0;77.}78.79.void Flowshop::Backtrack(int i)80.{81.if (i>n)82. {83.for (int j=1; j<=n; j++)84. {85. bestx[j] = x[j];86. }87. bestf = f;88. }89.else90. {91.for (int j = i; j <= n; j++)92. {93. f1+=M[x[j]][1];94.//机器2执行的是机器1已完成的作业,所以是i-195. f2[i]=((f2[i-1]>f1)?f2[i-1]:f1)+M[x[j]][2];96.97. f+=f2[i];98.if (f < bestf)//剪枝99. {100. Swap(x[i], x[j]); 101. Backtrack(i+1); 102. Swap(x[i], x[j]); 103. }104. f1-=M[x[j]][1];105. f-=f2[i];106. }107. }108.}109.110.int Flow(int **M,int n,int bestx[]) 111.{112.int ub=30000;113.114. Flowshop X;115. X.n=n;116. X.x=new int[n+1];117. X.f2=new int[n+1];118.119. X.M=M;120. X.bestx=bestx;121. X.bestf=ub;122.123. X.f1=0;124. X.f=0;125.126.for(int i=0;i<=n;i++)127. {128. X.f2[i]=0,X.x[i]=i;129. }130. X.Backtrack(1);131.delete []X.x;132.delete []X.f2;133.return X.bestf;134.}135.136.template <class Type>137.inline void Swap(Type &a, Type &b) 138.{139. Type temp=a;140. a=b;141. b=temp;142.}由于算法Backtrack在每一个节点处耗费O(1)计算时间,故在最坏情况下,整个算法计算时间复杂性为O(n!)。
《算法设计与分析》课程中“回溯法”教学探讨
vi akrc it ) odb c t k(n a t
{ i t )otu( ) f( >n up tx ;
2 1 采 用传 统教 学方 法 , . 强化 习题 讨 论 实 验 的 目的是 让学 生 自己动 手完 成任 务 , 同于课 堂教 学学生 是 实验课 的主人 , 对学生 算法 中有 不 针
问题的部分 , 教师应及时加以纠正 , 并以此展开讨论 , 传统教学方法的优点在于条理清楚 , 思路清晰, 教 师在板书时学生可以思考 , 而并非教师用课件直接将算法或程序演示 给学生。例如在做教材 4 9 . 磁带
子树 , 续按 深度 优 先策 略搜 索 。 继
由于回溯法在许多知识领域有广泛 的应用背景 , 故从各级 中小学生信息学竞赛到 A M IP C /C C国际 大学生程序设计竞赛 中均能发现其踪迹 _ 。它可 以解决许多看上去无法处理的问题 , 5 J 另外一些可用类 似于动态规划解决的问题也可用其处理 , 因此掌握回溯法 的原理与编程技巧 , 是程序设计的基本功 。与 动态规划法的状态转移方程因问题而异相比, 回溯法显得相对简单 , 大多数学生经过一段时间的训练均
成 该题 。
对 一些 依 靠搜 索 而不是 直接 套用 子集 树及 排列 树 的 问题 , 加 以启 发诱 导 , 要 及 O I 1 木棍拼接问题。这两题不能完全按照前期所讲授 的子集树及排列树问题 的套路求 解, 启发 学 生这 两题 共 同点是 采用 深度 优先 搜 寻原则 , 回溯 中加 人适 当 的剪枝 函数 , 在 如买 鱼 问题 中鱼 互不冲突 , 木棍拼接问题 中拼接后剩余长度为当前长度 , 即可完成。当学生明白并非所有 的搜索均是照 以前的套路做时, 再让他们做书中 5 1 .5整数变换问题 , 52 世界名画陈列馆问题。为了让学生拓展 及 .5
最佳调度的回溯算法
实验目的:理解回溯法的原理,掌握调度问题的处理方法,实现最佳调度问题的回溯解决。
问题定义输入:1.任务数N2.机器数M3.随机序列长度t[i],其中t[i]=x表示第i个任务完成需要时间单位x,输出:1.开销时间besttime,表示最佳调度需要时间单位2.最佳调度序列bestx[],其中bestx[i]=x,表示将第i个任务分配给第x个机器执行。
实验思想解空间的表示:一个深度为N的M叉树。
基本思路:搜索从开始结点(根结点)出发,以DFS搜索整个解空间。
每搜索完一条路径则记录下besttime 和bestx[]序列开始结点就成为一个活结点,同时也成为当前的扩展结点。
在当前的扩展结点处向纵深方向移至一个新结点,并成为一个新的活结点,也成为当前扩展结点。
如果在当前的扩展结点处不能再向纵深方向扩展,则当前扩展结点就成为死结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展结点;直至找到一个解或全部解。
测试数据及结果本测试的硬件以及软件环境如下CPU:PM 1.5G; 内存:768M;操作系统:windows xp sp2;软件平台:JDK1.5;开发环境:eclipse如图1所示:即为求任务数为10机器数为5的最佳调度的算法结果。
图1实验结论以及算法分析通过测试证明算法正确有效。
性能分析的方法:使用JDK 1.5的System.nanoTime(),计算算法消耗的时间,以此来评价算法。
(该方法在JDK1.5以下的版本中不支持)为了不影响算法的准确度,在测试的过程我们注释掉了打印随机字符串的步骤。
由于没有使用限界函数进行优化,算法时间和空间复杂度呈指数级增长。
所以该算法不适合较大规模的计算。
图2图2 蓝线表示机器数一定M=3时,n增大时求解最佳调度对所消耗的时间,该趋势随着指数增加。
图3图3表示任务数N一定时随着M的增大的增长曲线。
图2和图3表明该程序的时间复杂度与理论分析相符合。
组合优化问题的算法与求解
组合优化问题的算法与求解组合优化问题是一类需要在给定的约束条件下找到最优解的问题。
这些问题在现实生活中有着广泛的应用,比如物流配送问题、旅行商问题等等。
本文将介绍几种常见的组合优化问题的算法以及它们的求解方法。
一、贪婪算法贪婪算法是一种简单而高效的求解组合优化问题的方法。
它通过在每一步选择当前看起来最优的解决方案,逐步建立起最终的解。
贪婪算法通常具有快速的执行速度和较好的近似解质量。
例如,对于旅行商问题,贪婪算法可以从一个起点开始,每次选择离当前位置最近的未访问节点作为下一个访问节点,直到所有节点都被访问过。
这样,贪婪算法可以得到一个近似的最短路径。
二、回溯算法回溯算法是一种穷举搜索的方法,它通过逐个尝试所有可能的解决方案,并逐步剪枝以减少搜索空间。
回溯算法通常适用于组合优化问题的求解,尤其是在问题规模较小的情况下。
以0-1背包问题为例,回溯算法可以通过穷举所有可能的物品选择方式,计算其总价值,并在搜索过程中剪枝以提高效率。
回溯算法的优势在于能够找到最优解,但在问题规模较大时,耗时较长。
三、动态规划算法动态规划算法是一种将问题分解为子问题并记录子问题结果的方法。
它适用于能够将原问题分解为相互重叠的子问题,并利用子问题的解来推导原问题的解。
比如在背包问题中,动态规划算法可以通过定义状态转移方程来解决。
设dp[i][j]表示在前i个物品中选择总重量不超过j的情况下的最大价值,则有以下状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])通过填表计算,可以获得最终的最优解。
四、遗传算法遗传算法是一种模拟自然选择和遗传机制的搜索算法。
它通过模拟生物种群的遗传、变异、选择等过程,逐步演化出最优解。
遗传算法在求解组合优化问题时,通过编码将解空间中的解表示成染色体,并利用交叉、变异等遗传操作来搜索更优的解。
通过不断迭代,遗传算法能够找到较好的解,但无法保证找到全局最优解。
最佳调度问题(回溯算法)
if( time_machine[i] > time_max_K )
time_max_K = time_machine[i];
}
if(time_max_K<time_min)
{
time_min = time_max_K;
for(inti=1;i<=N;i++)
Res[i] = x[i];
}
}
else
{
for(inti=1;i<=K;i++)
{
x[k] = i;
if( placetest(k) )
{
time_machine[i] += t[k];
Backtrack(k+1,t,x);
time_machine[i] -= t[k];
}
}
}
}
三、结果与分析:
附录(源代码)
算法源代码(C++描述)
/*
}
if(time_max_K > time_min)
return false;
else
return true;
}
void Backtrack(int k,int t[],int x[])
{
if(k > N )
{
int time_max_K = time_machine[1];
for(int i=2;i<=K;i++)
结点;直至找到一个解或全部解。
二、核心代码:
boolplacetest(intk)
{
inttime_max_K = time_machine[1];
for(inti=2;i<=K;i++)
回溯算法解决批处理作业
回溯算法---解决批处理作业调度问题1、问题的描述n个作业{1, 2, …, n}要在两台机器上处理,每个作业必须先由机器1处理,然后再由机器2处理,机器1处理作业i所需时间为ai,机器2处理作业i 所需时间为bi(1≤i≤n),批处理作业调度问题要求确定这n个作业的最优处理顺序,使得从第1个作业在机器1上处理开始,到最后一个作业在机器2上处理结束所需时间最少。
利用回溯法解决批作业最优调度问题。
算法思想:对于有n个不同的任务,搜索出其最佳排列,属于一棵排列树搜索问题。
采用回溯法,搜索到第t层时,当已经是叶子节点时,说明该路径就是当前情况下的最优解。
当t不是叶子节点时,依次按照一定的顺序执行当前剩下的任务,将剩下的任务全部遍历一遍。
计算执行当前任务后,各个时间点的变化。
如果该层某个节点的任务执行之后,依然符合剪枝函数,则将当前的策略顺序做出调整,将刚刚执行的那个节点的任务序号放置到当前,然后继续向下进行搜索。
剪枝函数:当当前节点的总时间已经大于已找到的最优时间,则该节点后面的节点都不用进行搜索。
直接回溯。
2、代码及注释#include <iostream>using namespace std;#define MAX 1111111111//宏定义设置一个在适当范围内足够大的数字,方便以后进行比较//定义全局变量,包含如下int* x1;//作业Ji在机器1上的工作时间;int* x2;//作业Ji在机器2上的工作时间;int number=0;//作业的数目;int* xOrder;//作业顺序;int* bestOrder;//最优的作业顺序;int bestValue=MAX;//最优的时间;int xValue=0;//当前完成用的时间;int f1=0;//机器1完成的处理时间;int* f2;//第i阶段机器2完成的时间;//定义一个递归调用函数,找出最优void BackTrace(int k){if (k>number)//算法搜索至叶子结点,{for (int i=1;i<=number;i++){bestOrder[i]=xOrder[i];//得到一个新的作业调度方案。
第五章 回溯法
• Cr=C=30,V=0
C为容量,Cr为剩余空间,V为价值。 • A为唯一活结点,也是当前扩展结点。
H D 1 0 I 1
1 B 0 E 1 0 J K
A
0 C 1 F 1 0 L M N 0 G 1 0 O
5.1 回溯法的算法框架
• n=3, C=30, w={16,15,15}, v={45,25,25}
理论上
寻找问题的解的一种可靠的方法是首先列出所有候选解,然后依次检查每一个, 在检查完所有或部分候选解后,即可找到所需要的解。
但是
当候选解数量有限并且通过检查所有或部分候选解能够得到所需解时,上述方
法是可行的。
若候选解的数量非常大(指数级,大数阶乘),即便采用最快的计算机也只能 解决规模很小的问题。
显约束
对分量xi的取值限定。
隐约束 为满足问题的解而对不同分量之间施加的约束。
5.1 回溯法的算法框架
解空间(Solution Space)
对于问题的一个实例,解向量满足显式约束条件的所有多元组,构成了该 实例的一个解空间。 注意:同一问题可有多种表示,有些表示更简单,所需状态空间更小(存储 量少,搜索方法简单)。
回溯法引言
以深度优先的方式系统地搜索问题的解的算法称为回溯法 使用场合
对于许多问题,当需要找出它的解的集合或者要求回答什么解是满足某些
约束条件的最佳解时,往往要使用回溯法。 这种方法适用于解一些组合数相当大的问题,具有“通用解题法”之称。 回溯法的基本做法 是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。
一个正在产生儿子的结点称为扩展结点
活结点(L-结点,Live Node)
一个自身已生成但其儿子还没有全部生成的节点称做活结点
回溯算法详解
回溯算法详解
回溯算法是一种常用的解决问题的方法,它的目的是在一个大的问题空间中寻找到一个解决方案。
回溯算法的基本思想是穷举所有可能的解决方案,直到找到符合条件的解决方案为止。
回溯算法的实现通常包括两个部分:状态表示和状态转移。
状态表示是指将问题的解答空间表示为一个状态树,每个节点表示一个状态,状态转移是指从一个节点转移到另一个节点的过程。
回溯算法的实现过程通常包括三个步骤:选择、回溯和剪枝。
选择是指从当前状态节点选择一个扩展节点作为下一步的状态,回溯是指从一个状态节点返回到它的父节点,剪枝是指在搜索过程中对一些不可能达到目标的状态进行剪枝。
回溯算法常常用于求解组合、排列、子集、划分等问题。
由于回溯算法的时间复杂度很高,因此在实际应用中往往需要结合其他优化算法来提高效率。
总的来说,回溯算法是一种通用的算法,它可以解决许多不同类型的问题。
只要能够将问题的解答空间表示为一个状态树,并且能够找到一种回溯的方法来搜索这个状态树,就可以使用回溯算法来求解问题。
- 1 -。
最佳调度问题(回溯法)
最佳调度问题(回溯法)⼀、实验内容运⽤回溯法解决0-1背包问题(或装载问题、或批处理作业调度、或旅⾏售货员问题)使⽤回溯法解决批处理作业调度问题⼆、所⽤算法基本思想及复杂度分析1.算法基本思想从⼀条路往前⾛,能进则进,不能进则退回来,换⼀条路再试。
确定了解空间的组织结构后,回溯法从根节点出发,以深度优先搜索⽅式搜索整个解空间。
回溯法以这种⼯作⽅式递归地在解空间中搜索,直到找到所要求的解或解空间所有解都被遍历过为⽌。
2.问题分析及算法设计问题分析:(1)给定n个作业的集合{J1,J2,…,Jn}。
每个作业必须先由机器1处理,然后由机器2处理。
作业Ji需要机器j的处理时间为tji。
(2)对于⼀个确定的作业调度,设Fji是作业i在机器j上完成处理的时间。
(3)所有作业在机器2上完成处理的时间和称为该作业调度的完成时间和。
(4)批处理作业调度问题要求对于给定的n个作业,制定最佳作业调度⽅案,使其完成时间和达到最⼩。
算法设计:算法复杂度分析由于回溯算法在每⼀个结点处耗费O(1)计算时间,故在最坏情况下,整个算法的时间复杂性为O(n!).三、源程序核⼼代码及注释(截图)四、运⾏结果五、调试和运⾏程序过程中产⽣的问题及解决⽅法,实验总结(5⾏以上)调试和运⾏程序时没有多⼤的问题,主要是调试的步骤多,容易看错,从⽽理解错,解决⽅法:我是为每⼀个变量都添加了监视,⼀个⼀个的调试下去,再结合⾃⼰画的草图,⼀个的去分析,当正确的分析出⼀个分⽀后,就会发现,后⾯基本上都是重复的实现着前⾯的基本操作。
经过这次实验对于回溯法解问题时,⾸先应该明确问题的解空间,⼀般说来,解任何问题都有⼀个⽬标,在约束条件下使⽬标达到最优的可⾏解称为该问题的最优解。
#include<bits/stdc++.h>using namespace std;int n, k;int a[25];//任务完成的时间int x[25];//当前任务完成的时间int result = 410;//完成全部任务的最早时间void Backtrack(int num, int t){if (num > n)//到达叶⼦节点{if (t < result){//将最少时间赋值给resultresult = t;}}if (t >= result){//当⼤于时,直接跳出递归return;}for (int i = 0; i < k; i++){if (x[i] + a[num] < result){//看是否剪枝//当前任务完成时间+前⾯任务完成时间⼩于总时间时x[i] += a[num];//继续搜索下去Backtrack(num + 1, max(t, x[i]));//回溯x[i] -= a[num];//返回最初的状态}}}int main(){cin >> n >> k;for (int i = 0; i < n; i++){cin >> a[i];}Backtrack(0, 0);cout << result << endl;return 0;}。
回溯法 -数据结构与算法
回溯法-数据结构与算法定义:回溯法(探索与回溯法)是一种选优搜索法,按选优条件向前搜索,以达到目标。
但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
1、回溯法适用:有许多问题,当需要找出它的解集(全部解)或者要求回答什么解是满足某些约束条件的最优解时,往往要使用回溯法。
2、有组织的穷举式搜索:回溯法的基本做法是搜索或者有的组织穷尽搜索。
它能避免搜索所有的可能性。
即避免不必要的搜索。
这种方法适用于解一些组合数相当大的问题。
3、搜索解空间树:回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。
算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。
如果肯定不包含(剪枝过程),则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
为了实现回溯,我们先弄明白以下两个问题:1)首先应该明确问题的解空间。
2)其次是组织解空间以便它能用以被搜索到。
这个空间必须至少包含一个解(可能是最优的)。
一个复杂问题的解决往往由多部分构成,即,一个大的解决方案可以看作是由若干个小的决策组成。
很多时候它们构成一个决策序列。
解决一个问题的所有可能的决策序列构成该问题的解空间。
解空间中满足约束条件的决策序列称为可行解。
一般说来,解任何问题都有一个目标,在约束条件下使目标值达到最大(或最小)的可行解称为该问题的最优解。
在解空间中,前k项决策已经取定的所有决策序列之集称为k定子解空间。
0定子解空间即是该问题的解空间。
问题的解空间通常是在搜索问题的解的过程中动态产生的,这是回溯算法的一个重要特性。
解空间的确定与我们对问题的描述有关。
如何组织解空间的结构会直接影响对问题的求解效率。
这是因为回溯方法的基本思想是通过搜索解空间来找到问题所要求的解。
一般地,可以用一棵树来描述解空间,称为解空间树。
回溯法的效率分析
算法Estimate从解空间树的根结点开始选取一条随机路径, 计算回溯法生成的结点总数m。 Estimate (int n, Type *x) { int m=1,r=1,k=1;
j0 i0
1
1
2
2
3
3
4
4
这5条5 随机路径可以得到5的平均值为1702.
6
7
8
(8,6,4,3,2)=1977
(8,5,3,2,2,1,1,1)=2329
回溯法产生的结点数m是解空间树的结点总 数的1.55%左右。说明回溯法的效率大大高 于穷举法。
• 当用回溯法求解某一具体问题时,可用算法 Estimate估算回溯法生成的结点数。
• 若要估算得更精确些,可选取若干条不同的随 机路径(通常不超过20条),分别对各随机路 径估计结点总数,然后再取这些结点总数的平 均值,得到m的估算值。
• 例:8后问题
• 利用显约束排除那些有2个皇后在同一行或同 一列的方法,也有8!种不同的方法。8后问题 的解空间树的结点总数是:
7j
1 (8 i))
1
2
2
2
3
3
3
4
4
4
8后5 问题的解6 空间树的5 结点总6 数是 5
7j
7
1 ( (8 i)) 109601 (8,5,4,3,2)=1649 (8,5,3,1,2,1)=769 (8,6,4,2,1,1,1)=1785
回溯法的效率分析
一个回溯算法的效率在很大程度上依赖于以下几个因素:
回溯法详细讲解
子集树与排列树
遍历子集树需O(2n)计算时间
void backtrack (int t) { if (t>n) output(x);
else for (int i=0;i<=1;i++) { x[t]=i; if (legal(t)) backtrack(t+1); } }
遍历排列树需要O(n!)计算时间
35
约束函数?
构造最优解
在类Loading中增加两个私 有数据成员x和bestx。
» X记录从根至当前结点的 路径,x[i]=0/1;[1 0 1 1 0]
» Bestx记录当前最优解,算 法搜索到达一个叶子结点 处,就修正besx的值。
迭代回溯
tji 机器1 机器2
作业 2
22
回溯法的求解过程
回溯法的求解过程
5.1 回溯算法的基本框架
3 递归回溯
递归回溯
5.1 回溯算法的基本框架
4 迭代回溯
迭代回溯
5.1 回溯算法的基本框架
5 子集树与排列树
子集树:当所给的问题是从n个元素 的集合S中找出满足某种性质的子集 时,相应的解空间称为子集树。 排列树:当所给的问题是确定n个元 素满足某种性质的排列时,相应的 解空间树成为排列树。
void backtrack (int t) { if (t>n) output(x);
else for (int i=t;i<=n;i++) { swap(x[t], x[i]); if (legal(t)) backtrack(t+1); swap(x[t], x[i]); } }
回溯法的应用例子
信息学竞赛中的搜索与回溯算法
信息学竞赛中的搜索与回溯算法在信息学竞赛中,搜索与回溯算法起着重要的作用。
这些算法通过遍历可能的解空间来寻找最优解,解决了许多实际问题。
本文将介绍搜索与回溯算法的基本原理、应用场景以及算法的优化方法。
一、搜索算法搜索算法通常用于在给定的搜索空间中查找目标解。
常见的搜索算法包括深度优先搜索(DFS)、广度优先搜索(BFS)和启发式搜索等。
1. 深度优先搜索(DFS)深度优先搜索从根节点开始,沿着一条路径直到达到叶子节点或目标节点为止,然后回溯到上一个节点,继续搜索其他路径。
DFS算法非常适用于解决问题的完整解存在于较深路径的情况,例如迷宫问题、八皇后问题等。
2. 广度优先搜索(BFS)广度优先搜索从根节点开始,逐层扩展搜索,直到找到目标解或者搜索空间被完全遍历。
BFS算法适用于解决问题的完整解存在于较浅路径的情况,例如最短路径问题、迷宫最短路径问题等。
3. 启发式搜索启发式搜索通过使用启发函数来评估搜索的方向和选择。
它常用于解决复杂问题,如人工智能、路径规划等。
A*算法是一种常见的启发式搜索算法,它通过估计从当前节点到目标节点的代价来选择下一个节点。
二、回溯算法回溯算法是一种通过不断尝试所有可能解的方法,直到找到满足条件的解或遍历所有可能解的算法。
它常用于组合优化问题、排列问题等。
回溯算法的基本思想是通过逐步构建解空间,并在每一步选择一个可能的解,继续向下搜索。
如果当前选择导致无法满足条件,就回溯到上一步,尝试其他的选择。
回溯算法的典型应用包括全排列问题、子集问题和图的着色问题等。
它在信息学竞赛中广泛应用,可以有效地解决各种组合问题。
三、搜索与回溯算法的优化在实际应用中,搜索与回溯算法可能会面临解空间过大、搜索耗时长的问题。
为了提高算法的效率,可以采取以下优化方法。
1. 剪枝剪枝是指在搜索过程中,通过一些条件判断来减少搜索的路径,以避免不必要的计算。
剪枝可以根据问题的特点设计,例如对于排列问题,可以通过检查当前选择是否合法来剪枝。
回溯算法与深度优先搜索
回溯算法与深度优先搜索回溯算法(backtracking)和深度优先搜索(DFS)是两种在计算机科学中常用的问题解决方法。
它们在不同的领域和场景中都有着广泛的应用。
本文将详细介绍回溯算法与深度优先搜索的概念、原理及应用,并探讨它们之间的关系。
1. 回溯算法回溯算法是一种通过不断地尝试所有可能的解决方案来求解问题的方法。
在回溯算法中,我们从解空间的一点出发,逐步扩展搜索范围,并在搜索的过程中不断检查当前状态是否满足问题的要求。
如果当前状态不满足要求,则撤销上一步的操作,回溯到上一个状态,并继续搜索其他可能的解决方案。
回溯算法通常通过递归的方式实现。
在每一层递归中,我们选择一个可能的解决方案,并继续向下一层递归搜索。
如果搜索成功,则得到了一个解决方案;如果搜索失败,则回溯到上一层,选择其他的解决方案继续搜索。
回溯算法具有广泛的应用,如组合问题、排列问题、子集问题等。
它的优点是能够找到所有可能的解决方案,但缺点是搜索的过程比较耗时。
2. 深度优先搜索深度优先搜索是一种优先遍历深度的搜索方法。
在深度优先搜索中,我们从初始状态开始,不断选择可行的动作直到无法继续为止,然后回溯到上一个状态,并选择其他的动作继续搜索。
这个过程类似于在图中沿着一条路径一直向下搜索直到达到叶子节点,然后返回上一层,选择其他的路径继续搜索。
深度优先搜索通常通过递归或使用栈的数据结构实现。
在每一步搜索中,我们选择一个可行的动作,并将状态从一个节点转移到另一个节点。
如果搜索成功,则得到了一个解;如果搜索失败,则回溯到上一个状态。
深度优先搜索在图遍历、路径搜索等问题中有着广泛的应用。
它的优点是搜索效率较高,但缺点是可能会陷入局部最优解,无法找到全局最优解。
3. 回溯算法与深度优先搜索的关系回溯算法和深度优先搜索有着密切的关系。
在很多情况下,回溯算法可以看作是深度优先搜索的一种特殊形式。
回溯算法的核心思想是尝试所有可能的解决方案,并通过回溯到上一个状态来继续搜索。
第5章 回溯法ppt课件
2x3=2
x3=4 x3=2 x3=3
3
5
x4=4 x4=3
8 x4=4
10 13 15 x4=2 x4=3 x4=2
4
6
9
11 14 16
迷宫问题
演示
5.1 回溯法的算法框架
问题的解空间〔1)
1. 解向量:问题的解用向量表示
(x1, x2, …, xk) 模。 2. 约束条件
子树; 5. (2)限界函数:某个函数表达式或关系式。 6. 不真时,用于剪去得不到最优解的子树。 7. 回溯法:具有限界函数的深度优先搜索方法
回溯法的基本思想
1. 以深度优先方式搜索解空间。 2. 开始时,根结点为活结点,也是当前的扩展结点。 3. 对扩展结点,寻找儿子结点: 4. 如找到新结点,新结点成为活结点并成为扩展
子集树 void backtrack (int t){
if (t>n) output(x); else
for (int i=0;i<=1;i++) { x[t]=i; if (legal(t)) //若合法 backtrack(t+1);
} }
排列树 void backtrack (int t){
if (t>n) output(x); else
1装载问题2批处理作业调度3n后问题401背包问题5最大团问题6图的m着色问题7旅行售货员问题n皇后问题国际象棋中的皇后在横向直向和斜向都能走步和吃子问在nn格的棋盘上如何摆上n个皇后而使她们都不能相互吃掉
第5章 回溯法
上海大学计算机学院
学习要点与要求
• 掌握与理解回溯法的DFS搜索策略与方法
• (1〕掌握递归回溯
车辆调度和路线优化的最优解算法
车辆调度和路线优化的最优解算法车辆调度和路线优化是物流管理中至关重要的一部分。
通过合理安排车辆的发车时间、路线规划以及货品配送,可以减少运输成本、提高运输效率并满足顾客的需求。
在实际应用中,为了达到最优的调度和路线安排,需要借助最优解算法。
本文将介绍一种用于车辆调度和路线优化的最优解算法,并分析其实际应用。
一、车辆调度和路线优化问题的描述车辆调度和路线优化问题是指在给定一批货物和若干配送点的情况下,如何合理地安排车辆的发车时间和路线,以最小化总运输成本或最大化运输效率。
在实际应用中,该问题常常是一个组合优化问题,涉及到车辆的数量、容量、时间窗口等约束条件。
二、最优解算法的原理最优解算法旨在通过计算机程序,找到车辆调度和路线优化问题的最优解。
常用的最优解算法包括遗传算法、模拟退火算法、禁忌搜索算法等。
下面以遗传算法为例,介绍最优解算法的原理。
1. 初始化种群首先,需要随机生成若干个初始解(种群),每个初始解代表一种车辆的发车时间和路线安排方式。
2. 适应度评估对于每个解,根据其运输成本或运输效率等指标进行评估,得到适应度值。
适应度值越高,则说明该解越优秀。
3. 选择操作按照适应度值对种群进行排序,选择适应度较高的一部分个体作为父代,用于产生下一代个体。
4. 交叉操作从父代个体中随机选择两个个体进行交叉操作,生成两个子代个体。
5. 变异操作对子代个体进行变异操作,引入一定程度的随机性,以增加解空间的搜索能力。
6. 更新种群将父代和子代个体合并,得到新的种群。
7. 循环迭代重复执行2-6步骤,直至满足停止条件(如达到最大迭代次数)。
三、最优解算法在车辆调度和路线优化中的应用最优解算法在车辆调度和路线优化中有着广泛的应用。
通过对大量的发车时间和路线安排方案进行计算和优化,可以找到最优解,从而达到降低运输成本、提高运输效率的目的。
1. 车辆调度优化通过最优解算法,可以得到最佳的发车时间安排方案,合理分配车辆的出发时间,避免车辆之间的空载和重载情况,减少运输成本。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
《算法设计与分析》上机报告
姓名:张先荣学号:SA16225439 日期:2016/12/20
上机题
实验七:最佳调度问题(回溯算法) 目:
实验环境:
CPU:coreI7 ; 内存: 8G ;操作系统:Win10 ;软件平台Visual studio 2013 ;
一、算法设计与分析:
题目:
最佳调度问题(回溯算法)
设有n个任务由m个可并行工作的机器来完成,完成任务i需要时间为ti。
试设计一个算法找出完成这个任务的最佳调度,使完成全部任务的时间最早。
算法思想:
解空间的表示:
一个深度为N的M叉树。
int N;//任务数
int M;//机器数
int best;//最优值
int[] t;//每个任务所需要的时间序列
int[] len;//每台机器所需要的时间
int[] x;//当前路径
int[] bestx;//最优调度
基本思路:
1、搜索从开始结点(根结点)出发,以DFS搜索整个解空间。
2、每搜索完一条路径则记录下t和best序列,开始结点就成为一个活结点,
同时也成为当前的扩展结点。
在当前的扩展结点处向纵深方向移至一个新结点,
并成为一个新的活结点,也成为当前扩展结点。
3、如果在当前的扩展结点处不能再向纵深方向扩展,则当前扩展结点就成为死结点。
此时,应往回移动(回溯)至最近的一个活结点处,并使这个活结点成为当前的扩展
结点;直至找到一个解或全部解。
二、核心代码:
1.如图所示,即为回溯算法,以深度优先遍历方式遍历解空间树,每次向下遍历一层,意味着记录一次任务的完成序列。
Len加上任务序列的时间T,如果不是最优的,回溯,len减去任务的序列时间T
2.计算任务完成的时间。
某个机器时间最大,则他就是任务结束的时间。
3.解空间如图。
在这个图中能清晰地说明问题。
3叉树,机器数为3.深度为4,总共4层,则任务数为4,用3台机器去调度4个任务,把这棵树深度遍历,最后选出最优值。
三、结果与分析:
题目一:
如图所示,当任务数为10,机器数为7,总共耗时34.0782ms,最有时间为95单位时间,每个任务所花费时间就是随机数生成的时间序列是:67,45,80,32,59,95,37,46,28,20.机器调度顺序是1,2,3,2,4,5,6,6,1,4
如图所示,当任务数为20,机器数为2,总共耗时18.2999ms,最有时间为474单位时间,每个任务所花费时间就是随机数生成的时间序列是:73,31,66,7,50,9,11,53,66,90,99,12,37,31,31,38,9,82,60,93.
如图所示,当任务数为20,机器数为4,总共耗时57835.5837ms,
总结:
当任务数大于20,机器数大于5时,系统崩溃,所以回溯算法的时间复杂度较大。
附录(源代
码)算法源代码(C#描述)
public class BestSchedule
{
int N;//任务数
int M;//机器数
int best;//最优值
int[] t;//每个任务所需要的时间序列
int[] len;//每台机器所需要的时间
int[] x;//当前路径
int[] bestx;//最优调度
public void showTest()
{
N = 20;
Random r = new Random();
t=new int[N];//每个任务的时间
for (int i = 0; i < N; i++)
{
t[i] = r.Next(1,100);//随机数生成每个任务所需要的时间 }
len=new int[M];//记录每台机器已安排的时间
best = 9999999;
bestx = new int[N];
x = new int[N];
Stopwatch sw = new Stopwatch();
sw.Start();
backtrack(0);
sw.Stop();
TimeSpan ts2 = sw.Elapsed;
Console.WriteLine("程序运行总共花费{0}ms.",
ts2.TotalMilliseconds);
Console.WriteLine("最优时间是:");
Console.Write(best+" ");
Console.WriteLine("每个任务所花费的时间是:");
for (int i = 0; i < N; i++)
{
Console.Write(t[i] + " ");
}
Console.WriteLine();
Console.WriteLine("最佳调度序列是:");
for (int i = 0; i < N; i++)
{
Console.Write(bestx[i] + " ");
}
Console.ReadKey();
}
void backtrack(int dep)
{
if (dep == N)
{
int tmp = comp();
if (tmp < best)
{
best = tmp;
for (int i = 0; i < N; i++)
bestx[i] = x[i];
}
}
return;
}
for (int i = 0; i < M; i++)
{
len[i] += t[dep];
x[dep] = i + 1;
if (len[i] < best)
{
backtrack(dep + 1);
}
len[i] -= t[dep];
}
}
//计算完成任务的时间
int comp()
{
int temp = 0;
for (int i = 0; i < M; i++)
{
if (len[i] > temp)
{
temp = len[i];
}
}
return temp;
}
}
class Program
{
static void Main(string[] args)
{
BestSchedule bs = new BestSchedule(); bs.showTest();
}
}。