回溯法

回溯法
回溯法

回溯法

回溯法也是搜索算法中的一种控制策略,但与枚举法不同的是,它是从初始状态出发,运用题目给出的条件、规则,按照深度优秀搜索的顺序扩展所有可能情况,从中找出满足题意要求的解答。回溯法是求解特殊型计数题或较复杂的枚举题中使用频率最高的一种算法。

一、回溯法的基本思路

何谓回溯法,我们不妨通过一个具体实例来引出回溯法的基本思想及其在计算机上实现的基本方法。【例题12.2.1】n皇后问题

一个n×n(1≤n≤100)的国际象棋棋盘上放置n个皇后,使其不能相互攻击,即任何两个皇后都不能处在棋盘的同一行、同一列、同一条斜线上,试问共有多少种摆法?

输入:

n

输出:

所有分案。每个分案为n+1行,格式:

方案序号

以下n行。其中第i行(1≤i≤n)行为棋盘i行中皇后的列位置。

在分析算法思路之前,先让我们介绍几个常用的概念:

1、状态(state)

状态是指问题求解过程中每一步的状况。在n皇后问题中,皇后所在的行位置i(1≤i≤n)即为其时皇后问题的状态。显然,对问题状态的描述,应与待解决问题的自然特性相似,而且应尽量做到占用空间少,又易于用算符对状态进行运算。

2、算符(operater)

算符是把问题从一种状态变换到另一种状态的方法代号。算符通常采用合适的数据来表示,设为局部变量。n皇后的一种摆法对应1..n排列方案(a1,…,a n)。排列中的每个元素a i对应i行上皇后的列位置(1≤i≤n)。由此想到,在n皇后问题中,采用当前行的列位置i(1≤i≤n)作为算符是再合适不过了。由于每行仅放一个皇后,因此行攻击的问题自然不存在了,但在试放当前行的一个皇后时,不是所有列位置都适用。例如(l,i)位置放一个皇后,若与前1..l-1行中的j行皇后产生对角线攻击(|j-l|=|a j -i|)或者列攻击(i≠a j),那么算符i显然是不适用的,应当舍去。因此,不产生对角线攻击和列攻击是n皇后问题的约束条件,即排列(排列a1,…,a i,…,a j,…,a n)必须满足条件(|j-i|≠|a j-a i|) and (a i≠a j) (1≤i,j≤n)。

3、解答树(analytic tree)

现在让我们先来观察一个简单的n皇后问题。设n=4,初始状态显然是一个空棋盘。

此时第一个皇后开始从第一行第一列位置试放,试放的顺序是从左至右、自上而下。每个棋盘由4个数据表征相应的状态信息(见下图):

(××××)

其中第i(1≤i≤4)个数据指明当前方案中第i个皇后置放在第i行的列位置。若该数据为0,表明所在行尚未放置皇后。棋盘状态的定义如下

var

stack:array[1‥4]of integer;{stack[i]为i行皇后的列位置}

从初始的空棋盘出发,第1个皇后可以分别试放第1行的4个列位置,扩展出4个子结点。

在上图中,结点右上方给出按回溯法扩展顺序定义的结点序号。现在我们也可以用相同方法找出这些结点的第二行的可能列位置,如此反复进行,一旦出现新结点的四个数据全非空,那就寻到了一种满足题意要求的摆法。当尝试了所有可能方案,即获得了问题的解答,于是得到了下列图形。

该图形象一棵倒悬的树。其初始结点v1叫根结点,而最下端的结点v3、v5、v9、v13、v16、v17称为叶结点,其中2个数据全非零的叶结点,亦即本题的目标结点。由根结点到每一个目标结点之间,揭示了一种成功摆法的形成过程。显然,4皇后问题存在由v9、v13表示的二种方案。上图被称作解答树。树中的每一结点都是当前方案中满足约束条件的元素状态。除了根结点、叶结点以外的结点都称作分枝结点。分枝结点愈接近根结点者,辈分愈高;反之,愈远离根结点者,辈分愈低。上图中结点v7是结点v8的父结点(又称前件),结点v13是结点v12的子结点(又称后件)。某结点所拥有的子结点的个数称作该结点的次数。显而易见,所有叶结点的次数为0。树中各结点次数最大值,被称作为该树的次数。算符的个数即为结解答树的次数。由上图可见,4皇后的解答树是4次树。

一棵树中的某个分枝结点也可视作为“子根”,以该结点为根的树则称作“子树”。由以上讨论可以看出解答树的结构:

1、初始状态构成(主)树的根结点。对应于n皇后来说,初始时的空棋盘即为根结点;

2、除根结点以外,每个结点都具有一个、且只有一个父结点。对应于n皇后问题来说,置放i行皇后的子结点,只有在置放了前i-1行皇后的一个父结点基础上产生;

3、每个非根结点都有一条路径通往根结点,其路径长度(代价)定义为这条路径的边数。对应于n皇后来说,当前行序号即为路径代价。当路径代价为n+1时,说明n个皇后已置放完毕,一种成功的摆法产生。

有了以上的基础知识和对n皇后问题的初步分析,我们已经清楚地看到,求解n皇后问题,无非就是做两件事:

1、从左至右逐条树枝地构造和检查解答树t;

2、检查t的结点是否对应问题的目标状态;

上述两件事同时进行。为了加快检查速度,一般规定:

1、再扩展一个分枝结点前进行检查,只要它不满足约束条件,则不再构造以它为根的子树;

2、已处理过的结点若以后不会再用,则不必保留。即回溯过程中经过的结点不再保留。例如在上图中,当我们求出第一种摆法v1-v2-v3后,由于皇后置放第三行任何列位置都会产生攻击,因此舍弃该摆

法,开始寻求第二种摆法。从上图可看出,第二条路径为v1-v2-v4-v5,v3在第二种摆法中不再用到,不必保留,应当退回到v2状态,在第二行选择尚未使用过的列位置4,扩展出v4。一般来说,当求出一条路径后,必须从叶结点开始,沿所在路径回溯,回溯至第一个还剩有适用算符的分枝点(亦称为尚未使用过的通向右边方向的结点),从那里换上一个新算符,继续求下一条路径。

按上述规定对照上图,我们来具体分析4皇后的置放过程。初始状态(0,0,0,0)作为根结点v1,由此出发,置第1个皇后于第1行第1列位置。从(1,0,0,0)开始,第2个皇后相继选择了第2行的1、2列位置,由于会产生攻击,因此选择该行的列位置3放入,产生状态(1,3,0,0)。但是第3个皇后无论放入第3行哪列位置都难逃攻击,因此只得沿第一条路径回溯至第一个尚未用过的通向右边方向的分枝点v2,以寻求第二种摆法。从(1,0,0,0)状态换上新的列位置4,产生(1,4,0,0)。从(1,4,0,0)选择列位置2(由于列位置1产生攻击),产生(1,4,2,0)。由于第4个皇后无论置放第4行哪列位置都会产生攻击,第二种摆法失败,同样再从v5开始,沿第二条路径回溯。由于v2,v4都没有未使用的满足约束条件的算符(列位置)了,因此第一个分枝点是v1,从v1的(0,0,0,0)换上位置2,产生v6的(2,0,0,0)。这样依次使用满足约束条件的算符扩展下去,又得出第三条路径v1-v6-v7-v8-v9。可见,v9的(2,4,1,3)是一种成功的摆法。按上述规律不断回溯检查,直至得出第六条路径v1-v14-v17。沿路径从v17回溯,由于v14选择尚未用过的列位置3、4都会产生攻击,因此不再剩有适用的列位置了,只得回溯至v1。又因为v1已经选择了列位置4而无法再扩展,至此,求出了4皇后的所有可能摆法。

由上述扩展过程引出回溯法的基本思想:从左至右逐条树枝地构造和检查查找解答树,已处理过的结点若以后不会再使用则不必保留(一般说来,检查长度为n的树枝,只要保留n个结点就够了)。若按这种方式得到一条到达树叶的树枝t,实际上就得到了一条路径。然后沿树枝t回溯到第一个尚未使用过通往右边路径方向上的分枝点,并由此分枝点向右走一步,然后再从左至右地逐个进行构造和检查,直至达到叶子为止,这时又得到一条路径。按这种方法搜索下去,直至求出所有路径。显然用这种方法检查,在树枝左边的一切结点都已检查过,树枝右边的一切结点尚未产生出来。我们把这种不断“回溯”查找解答树中目标结点的方法,称作“回溯法”。

由上述算法思想,我们很容易想到,应选择怎样一种数据结构来存放当前路径上各结点的状态和算符?它应具有“后进先出”的特征,就象食堂里的一叠盘子,每次只许一个一个地往顶上堆,一个一个地从顶上往下取。这就是我们通常所说的栈。栈是一种线性表,所有进栈或出栈的数据都只能在表的同一端进行,就象堆盘子和拿盘子一样,都只能在顶端“堆上”或“取下”。这顶端叫“栈顶”,另一端叫“栈底”。Pascal编译系统内部,保留一部分内存用作栈区,存放过程和函数的值参以及过程和函数内部所说明的局部变量。每当一个过程和函数被启用时,系统就在栈顶分配一组值参和局部变量(进栈)。而当该过程或函数退出时,这些局部变量或值参就被消除(退栈)。我们为回溯法设计的一个递归过程run 就是利用系统的这一特性:

procedure run(当前状态);

var

i:integer;

begin

if 当前状态为边界

then begin

if 当前状态为最佳目标状态then 记下最优结果;

exit;{回溯}

end;{then}

for i←算符最小值to 算符最大值do

begin

算符i作用于当前状态,扩展出一个子状态;

if (子状态满足约束条件) and (子状态满足最优性要求)then run(子状态);

end;{for}

end;{run}

我们在应用回溯法求所有路径的算法框架解题时,应考虑如下几个重要因素:

⑴定义状态:即如何描述问题求解过程中每一步的状况。在n皇后问题中,将行位置l作为状态。如果扩展结点时参与运算的变量有多个,为了精简程序,增加可读性,我们一般将参与子结点扩展运算的变量组合成当前状态列入值参,以便回溯时能恢复递归前的状态,重新计算下一条路径;

⑵边界条件:即在什么情况下程序不再递归下去。在n皇后问题中,将l=n+1(产生一种成功摆法)作为边界条件。如果是求满足某个特定条件的一条最佳路径,则当前状态到达边界时并非一定意味着此时就是最佳目标状态。因此还须增加判别最优目标状态的条件;

⑶搜索范围:在当前状态不满足边界条件的情况下,应如何设计算符值的范围。换句话说,如何设定for 语句中循环变量的初值和终值。在n皇后问题中,l行的列位置i作为搜索范围,即1≤i≤n;⑷约束条件和最优性要求:所谓约束条件是指,当前扩展出一个子结点后应满足什么条件方可继续递归下去;如果是求满足某个特定条件的一条最佳路径,那么在扩展出某个子状态后是否继续递归搜索下去,不仅取决于子状态是否满足约束条件,而且还取决于子状态是否满足最优性要求。在n皇后问题中,将(l,i)置放皇后不产生攻击(att=false)作为约束条件;

⑸参与递归运算的参数:将参与递归运算的参数设为递归子程序的值参或局部变量。若这些参数的存储量大(例如数组)且初始值需由主程序传入,为避免内存溢出,则必须将其设为全局变量,且回溯前需恢复其递归前的值。在n皇后问题中,将皇后的行位置l和列位置i作为参与递归运算的参数;

虽然上述程序流程仅是一种“粗线素描”,但其编排直接面对问题,囊括回溯搜索的所有本质特征,并有意识地在问题与算法之间显现一种“隐约可见”的思维自由度,有利于启迪创造性。按照上述要求,我们设计了n皇后问题的递归子程序:

procedure run(l:integer);{从第l行出发,递归搜索所有摆法}

var

i:integer;

function att:boolean;{检查前l-1行,若产生攻击,置att为true;否则返回flase } begin

att←false;{攻击标志初始化}

for j←1 to l-1 do {搜索前l-1行}

if (abs(l-j)=abs(stack[j]-i))or(i=stack[j])

then begin {若l行的皇后与j行的皇后产生攻击,则返回true} att←true;break;

end;{then}

end;{att}

begin

if l=n+1 then {产生一种成功摆法}

begin

inc(total);writeln('no ',total);{累计方案数并输出该方案}

for j←1 to n do 输出第j行皇后的列位置为stack[j];

writeln;

exit;{回溯,求下一方案} end;{then}

for i←1 to n do

begin

stack[l]←i;{ (l,i)试放一个皇后}

if not att then run(l+1); { 若(l ,i)置放皇后不产生攻击,则搜索l+1行 } end ;{for}

二、回溯法的应用实例

在组成状态的各个元素中,如果有元素需要采用存储量大(例如数组、字符串)的数据类型,则应该避免将这些元素列为参或局部变量。因为系统栈区的容量极其有限,每次递归都要将这些大存储量的压入栈区,很容易产生内存溢出。不妨将它们设为设为全局变量来参与递归运算,但回溯前需要恢复其递归前的值。

【例题12.2.2】构造字串

生成长度为n 的字串,其字符从26个英文字母的前p(p ≤26)个字母中选取,使得没有相邻的子序列相等。例如p=3,n=5时

‘a b C b a ’满足条件

‘a b C b C ’不满足条件

输入:n ,p

输出:所有满足条件的字串

题解

1、检查当前字串是否符合条件

设当前串s=s 1‥s m-1s m 且s 1‥s m-1合理。按照下述方法判断s 是否仍然保持其合理性质:

分别判断s 的后缀中长度为1的两个相邻子串s m-1与s m 是否相等;长度为2的两个相邻子串s m-3s m-2与s m-1s m 是否相等,……‥,长度为??????2m 的两个相邻子串s 1‥??

????2m s 与12+??????m s ‥s m 是否相等。即s m-2l+1‥s m-1与s m-l+1‥s m 是否相等(1≤l ≤??

????2m )。若其中一旦出现了两个相邻子串相等的情况,则说明s 1‥s m 不合理;若经过??

????2m 次判断后未出现不合理情况,则说明s 1‥s m 满足条件。 2、回溯搜素满足条件的所有字串

我们从空串出发,逐个字符地延长字串。若当前字符添入后使得字串保持合理的性质,则添入该字符;否则改变字串。

状态:待扩展的字母序号at 。实际上字串s 亦参与了递归运算,但是由于该变量的存储量太大,因此我们将s 设为全局变量;

边界条件和目标状态:产生了一个满足条件的字串,即at=n+1;

搜索范围:第at 位置可填的字母集{'a'.. chr(ord('a')+p-1)};

约束条件:当前字串没有相邻子串相等的情况

var

n ,p :integer ; {字串长度和可选字母的个数} tl :longint ; { 满足条件的字串数} ed :char ; { 可选字母集中的最大字母}

s :string ; {满足条件的字串} procedure solve(at :integer); {递归扩展第at 个字母} var

ch :char ; i :integer ;

begin

if at=n+1 {若产生了一个满足条件的字串,则输出,满足条件的字串数+1} then begin

writeln(f,s); inc(tl);

exit {回溯}

end;{then}

for ch←'a' to ed do {搜索每一个可填字母} begin

s←s+ch;

i←1; { 检查当前字串是否符合条件} while (i<=at div 2) and

(copy(s,length(s)-i+1,i)<>copy(s,length(s)-2*i+1,i)) do inc(i);

if i>at div 2 then solve(at+1); {若当前字串符合条件,则递归扩展下一个字母} delete(s,length(s),1) {恢复填前的字串} end{for}

end;{solve}

begin

readln(n,p); {输入字串长度和前缀长短}

ed←chr(ord('a')+p-1); {计算可选字母集中的最大字母}

s←''; tl←0; {满足条件的字串初始化为空,字串数为0}

solve(1); {从第1个字母开始递归计算所有满足条件的字串}

writeln('Total:',tl); {输出满足条件的字串数}

end.{main}

在搜索过程中,如果扩展子状态的过程是在不同情况(即每种情况下的约束条件、扩展规则或搜索范围不同)下进行,则对当前状态分情形递归搜索

【例题12.2.3】数字排列

在n*n的棋盘上(1≤n≤10)填入1,2,...n*n共n*n个数,使得任意两个相邻的数之和为素数。例如,当n=2

其相邻数的和为素数的有:1+2,1+4,4+3,2+3

当n=4

在这里我们约定:左上角的格子里必须放数字1

程序要求:

输入:n

输出:若有多种解,则需输出第一行,第一列之和均为最小的排列方案;若无解,则输出"nO!"

题解

1、计算3‥2*n2-1的素数表su

搜索过程中需要反复进行素数判断。为了提高运算效率,不妨将2‥n2中的素数置入一个常量表su。设

var

f:boolean; { 素数标志}

su :set of byte ; { 素数表} 计算过程如下:

su ←[];

for i ←3 to n*n*2-1 do

begin

f ←true ;

for j ←2 to ??i do

if i mod j=0 then begin f ←false ; break ;end ;{then}

if f then su ←su+[i];

end ;{for}

2、回溯搜索填数方案

var

max ,sum :integer ; { 第1行、第1列的最小数和、当前数和} p :array[1..maxn*maxn]of boolean ; {p[i]—数i 已填标志}

b ,a :array[1..maxn ,1..maxn]of byte ; {最佳棋盘和当前棋盘}

我们首先将1填入(1,1);然后按照先第1行后第1列的顺序填入2*n-2个数;最后逐行填其他位置。 状态:(i ,j ,sum )。即欲填(i ,j ),目前第1行、第1列的数和为sum 。P 序列也应该是状态,但不能列入值参。原因是P 序列的内存开销太大,列入值参极易导致内存溢出。无奈之下,只能作为全局变量。当数值k’填入棋盘,p[k ’]←true ;但回溯时,必须通过p[k ’]←false 恢复递归前k’的未填状态;

边界:i>n ,即完成数据的填写。若满足条件,则当前方案作为最佳方案记下(max ←sum ;b ←a ); 目标:sum=n*(2*n-1)。若1…2*n-1填入第1行、第1列,则输出最佳棋盘b 并退出;

搜索范围:1≤k ≤???

?????22n 。由于棋盘的任意两个相邻的数之和为素数,因此棋盘的结构为 奇 偶 奇………

偶 奇 偶………

奇 偶 奇………

………………

由此可以看出,行号i 和列号j 同为奇或者同为偶时(not (odd(i) xor odd(j))),(i ,j )填奇数k’=2*k -1;否则(i ,j )填偶数k’=2*k ;

约束条件:

p[k’]=flase ,即目前棋盘未填入数据k’;

若满足约束条件,则k’ 填入(i ,j ),置k’ 填入标志(p[k’]←true )。然后分情形递归;

分情形递归:

①若(i=1)∧(a[i ,j]+a[i ,j-1]∈su)∧(sum+a[i ,j]

②若(j=1)∧(a[i ,j]+a[i-1,j]∈su)∧(sum+a[i ,j]

③否则填其它位置。如果(a[i ,j]+a[i-1,j]∈su)∧(a[i ,j]+a[i ,j-1]∈su),即上下两数之和为素数且左右两数之和为素数,则分情形递归:若当前行填完(j=n ),则递归计算(i+1,2,sum)状态;否则递归计算

(i ,j+1,sum )状态;

我们通过递归程序solve(i ,j ,sum)描述搜索过程:

procedure solve(i ,j :byte ;sum :integer);

var

k ,k ’:integer ;

begin

if i>n then {若完成数据的填写,则记下当前方案} begin

max ←sum ; b ←a ;

if max=n*(2*n-1) then {若1…2*n-1填入第1行、第1列,则输出最佳棋盘b 并退出} begin

for r ←1 to n do

begin

for s ←1 to n do write(b[r ,s]:5);

writeln ;

end ;{for}

halt ;

end ;{then}

exit ; {回溯} end ;{then}

for k ←1 to ???

?????22n do {枚举(i ,j )位置的所有可能填数} begin {否则计算欲填入(i ,j )的数据k ’}

if (odd(i) xor odd(j))

then k ’←2*k

else k ’←2*k-1;

if not p[k ’] then {若k ’未填过,则k ’ 填入(i ,j ),置k ’ 填入标志} begin

a[i ,j] ←k ’; p[k ’] ←true ;

if i=1 {若当前填第一行且左右两数之和为素数且第1行、第1列的数和仍为最小}

then begin

if (a[i ,j]+a[i ,j-1] in su)and(sum+a[i ,j]

if j=n { 若第一行填完,则填写(2,1);否则填写(i ,j+1)}

then solve(2,1,sum+a[i ,j])

else solve(i ,j+1,sum+a[i ,j]);

end {then }

else if j=1{若填第一列且上下两数之和为素数且第1行、第1列的数和仍为最小}

then begin

if ((a[i ,j]+a[i-1,j]) in su)and(sum+a[i ,j]

then if i=n

then solve(2,2,sum+a[i ,j]){若第一列填完,则填写(2,2)}

else solve(i+1,j ,sum+a[i ,j]); {否则填写(i+1,j )}

end{then}

else {否则填其它位置。如果上下两数之和为素数且左右两数之和为素数}

if (a[i ,j]+a[i-1,j] in su)and(a[i ,j]+a[i ,j-1] in su)

then if j=n then solve(i+1,2,sum){若当前行填完,则填写(i+1,2)}

else solve(i,j+1,sum); {否则填写(i,j+1)}

p[k’]←false; {恢复递归前k’的未填状态}

end;{then}

end;{else}

end;{solve}

3、主程序

初始时,计算计算3‥2*n2-1的素数表su,并设第1行、第1列的最小数和max为∞。然后按照试题要求将1填入(1,1),通过递归调用solve(1,2,1)计算填数方案。若递归后max仍为∞,则说明无解;否则棋盘b即为最佳方案:

fillchar(p,sizeof(p),false); { 初始时未填任何数据}

max←30000; { 第1行、第1列的最小数和初始化}

p[1] ←true; { 1填入(1,1)}

a[1,1] ←1;

solve(1,2,1); { 递归计算填数方案}

if max=30000 then writeln(’no’)

else for i←1 to n do begin {输出最佳方案b}

for j←1 to n do write(b[i,j]:5);

writeln;

end;{for}

三、回溯法的优化

但是,回溯法亦有其致命的弱点——时间效率比较数学解析法低。为了改善其时效,我们要尽可能减少分枝(减少解答树的次数)和增加约束条件(使其在保证出解的前提下尽可能“苛刻”);另外还可以从下述两个方面考虑优化

1、递归前对尚待搜索的信息进行预处理

如果搜索对象是通过某种运算直接得出其结果的,那么搜索前一般需进行预处理—通过相应运算将所有搜索对象的计算结果置入常量表,搜索过程中只要将当前搜索对象的结果值从常量表取出即可。这样可以显著改善搜索效率。否则,在搜索过程中每遇到这些对象都要计算,则会产生大量的重复运算。例如在【例题12.2.3】数字排列中将2‥n2中的素数置入一个常量表su,就是一种改善搜索效率的预处理。

2、记忆化搜索

如果解答树中存在一些性质相同的子树,那么,只要我们知道了其中一棵子树的性质,就可以根据这个信息,导出其它子树的性质。这就是自顶向下记忆化搜索的基本思想。

【例题12.2.1】序关系计数问题

用关系’<’和’=’将3个数a、b和C依次排列有13种不同的关系:

a

b

C

输入n

输出n个数依序排列时有多少种关系。

1、枚举所有序关系表达式

我们可以采用回溯法枚举出所有的序关系表达式。n个数的序关系表达式,是通过n个大写字母和连接各字母的n-1个关系符号构成。依次枚举每个位置上的大写字母和关系符号,直到确定一个序关系表达式为止。

由于类似于‘a=b’和‘b=a’的序关系表达式是等价的,为此,规定等号前面的大写字母在aSCII表中的序号,必须比等号后面的字母序号小。基于这个思想,我们很容易写出解这道题目的回溯算法:状态(Step,First,Can):其中Step表示当前确定第Step个关系符号;First表示当前大写字母中最

小字母的序号;Can是一个集合,集合中的元素是还可以使用的大写字母序号

边界条件(s tep=n):即确定了最后关系符号

搜索范围(First≤i≤n):枚举当前大写字母的序号

约束条件(i in Can):序号为i的大写字母可以使用

算法1:

procedure Count(Step,First,Can);{从当前状态出发,递归计算序关系表达式数} begin

if Step=n then begin {若确定了最后一个关系符号,则输出统计结果}

for i←First to n do if i in Can then Inc(Total);

Exit {回溯}

end;{then}

for i←First to n do {枚举当前的大写字母} if i in Can then begin {序号为i的大写字母可以使用}

Count(Step+1,i+1,Can-[i]);{添等于号}

Count(Step+1,1,Can-[i]) {添小于号}

End{then}

end;{ Count}

主程序调用Count(1,1,[1..n])后,Total的值就是结果。该算法的时间复杂度是W(n!)

2、粗略利用信息

算法1中存在大量冗余运算。如下图,三个方框内子树的形态完全一样。一旦我们知道了其中某一个方框内所产生的序关系数,就可以利用这个信息,直接得到另两个方框内将要产生的序关系数。

显然,在枚举的过程中,若已经确定了前k个数,并且下一个关系符号是小于号,这时所能产生的序关系数就是剩下的n-k个数所能产生的序关系数。

设i个数共有F[i]种不同的序关系,那么,由上面的讨论可知,在算法1中,调用一次Count(Step+1,1,Can-[i])之后,Total的增量应该是F[n-Step]。这个值可以在第一次调用Count(Step+1,1,Can-[i])时求出。而一旦知道了F[n-Step]的值,就可以用Total←Total+F[n-Step] 代替调用Count(Step+1,1,Can-[i])。这样,我们可以得到改进后的算法2。

算法2:

procedure Count(Step,First,Can); {Step,First,Can的含义同算法1}

begin

if Step=n then begin {若确定了最后一个关系符号,则输出统计结果}

for i←First to n do if i in Can then Inc(Total);Exit {回溯}

end;{then}

for i←First to n do {枚举当前的大写字母} if i in Can {序号为i的大写字母可以使用}

then begin

Count(Step+1,i+1,Can-[i]); {添等于号} if F[n-Step]=-1

then begin {第一次调用}

F[n-Step] ←Total ;

Count(Step+1,1,Can-[i]); {添小于号} F[n-Step] ←Total-F[n-Step] {F[n-Step]=Total 的增量}

end {then}

else Total ←Total+F[n-Step] {F[n-Step]已经求出}

end{then}

end ;{count}

开始,将F[0],F[1],…,F[n-1]初始化为-1。调用Count(1,1,[1..n])之后,Total 的值就是结果。算法2与算法1的差别仅限于程序中的粗体部分。算法2就是利用了F[0],F[1],…,F[n-1]的值,使得在确定添小于号以后,能够避免多余的搜索,尽快地求出所需要的方案数。该算法实质上就是自顶向

下记忆化方式的搜索,它的时间复杂度为W(2n )。同算法1相比,效率虽然有所提高,但仍不够理想。

3、充分利用信息

在搜索的过程中,如果确定在第k 个大写字母之后添加第一个小于号,则可得到下面两条信息:

第一条信息:前k 个大写字母都是用等号连接的。

第二条信息:在此基础上继续搜索,将产生F[n-k]个序关系表达式。

如上图所示,序关系表达式中第一个小于号将整个表达式分成了两个部分。由乘法原理易知,上图所示的序关系表达式的总数,就是图中前半部分所能产生的序关系数,乘以后半部分所能产生的序关系数。算法1-2实质上利用了第二条信息,直接得到图中后半部分将产生F[n-k]个序关系数,并通过搜索得到前半部分将产生的序关系数。但如果我们利用第一条信息,就可以推知图中前半部分将产生的序关系数,就是n 个物体中取k 个的组合数,即k n C 。这样,我们可以得到F[n] 的递推关系式:1]0[][][1=-=

∑=F k n F C n F n k k n ,其中 采用上述公式计算F[n]的算法记为算法3,它的时间复杂度是W(n 2)。

算法3:

var

Total :Comp ; {答案}

F :array[0..maxn] of Comp ; {F[i]为i 个数的序关系表达式个数} i ,j :Integer ;

x :Comp ;

begin

FillChar(F ,Sizeof(F),0); {F 初始化} F[0] ←1;

for i ←1 to n do begin {递推F 数组} F[i] ←0;

x ←1;

for j←1 to i do begin {计算F[i]}

x←x*(i-j+1)/j; {计算j

}

i C

F[i] ←F[i]+x*F[i-j]

End{for}

end;{for}

writeln(F[n]); {输出结果}

end;{main}

在优化算法1的过程中,我们通过利用F[0],F[1]…,F[n-1]的信息,得到算法2,时间复杂度也从W(n!)降到W(2n)。在算法2中,进一步消除冗余运算,就得到了W(n2)的算法3。算法3充分利用信息,通过两重循环的递推计算F[n],将时间复杂度降到W(n2),实现了程序的最优性要求。

回溯法论文-回溯法的分析与应用

沈阳理工大学算法实践与创新论文

摘要 对于计算机科学来说,算法的概念是至关重要的,算法是一系列解决问题的清晰指令,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。为了更加的了解算法,本篇论文中,我们先研究一个算法---回溯法。 回溯法是一种常用的重要的基本设计方法。它的基本做法是在可能的范围之内搜索,适于解一些组合数相当大的问题。圆排列描述的是在给定n个大小不等的圆 C1,C2,…,Cn,现要将这n个圆排进一个矩形框中,且要求各圆与矩形框的底边相切。圆排列问题要求从n个圆的所有排列中找出有最小长度的圆排列。图着色问题用数学定义就是给定一个无向图G=(V, E),其中V为顶点集合,E为边集合,图着色问题即为将V分为K个颜色组,每个组形成一个独立集,即其中没有相邻的顶点。其优化版本是希望获得最小的 K值。符号三角形问题要求对于给定的n,计算有多少个不同的符号三角形,使其所含的“+”和“-”的个数相同。 在本篇论文中,我们将运用回溯法来解决着图的着色问题,符号三角形问题,图排列问题,将此三个问题进行深入的探讨。 关键词: 回溯法图的着色问题符号三角形问题图排列问 题

目录 第1章引言 (1) 第2章回溯法的背景 (2) 第3章图的着色问题 (4) 3.1 问题描述 (4) 3.2 四色猜想 (4) 3.3 算法设计 (5) 3.4 源代码 (6) 3.5 运行结果图 (10) 第4章符号三角形问题 (11) 4.1 问题描述 (11) 4.2 算法设计 (11) 4.3 源代码 (12) 4.4 运行结果图 (16) 第5章圆的排列问题 (17) 5.1 问题描述 (17) 5.2 问题分析 (17) 5.3 源代码 (18) 5.4 运行结果图 (22) 结论 (23) 参考文献 (24)

实验6 子集和问题的回溯算法设计与实现(报告)

实验6 子集和问题的回溯算法设计与实现 一、实验目的 1、掌握回溯法解题的基本思想; 2、掌握回溯算法的设计方法; 3、针对子集和数问题,熟练掌握回溯递归算法、迭代算法的设计与实现。 二、实验内容 1、认真阅读教材或参考书, 掌握回溯法解题的基本思想, 算法的抽象控制策略; 2、了解子集和数问题及解向量的定长和变长状态空间表示; 3、针对解向量的定长表示, 设计状态空间树节点扩展的规范(限界)函数及实现方法; 4、分析深度优先扩展状态空间树节点或回溯的条件; 5、分析和设计生成解向量各分量可选值的实现方法; 6、设计和编制回溯算法的递归和迭代程序。 【实验题】: 组合数问题:找出从自然数1,2,…,n中任取r个数的所有组合。 三、算法的原理方法 回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。 当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。 如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。 在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。 可以采用回溯法找问题的解,将找到的组合以从小到大顺序存于a[0],a[1],…,a[r-1]中,组合的元素满足以下性质: (1)a[i+1]>a[i],后一个数字比前一个大; (2)a[i]-i<=n-r+1。 按回溯法的思想,找解过程可以叙述如下: 首先放弃组合数个数为r的条件,候选组合从只有一个数字1开始。因该候选解满足除问题规模之外的全部条件,扩大其规模,并使其满足上述条件(1),候选组合改为1,2。继续这一过程,得到候选组合1,2,3。该候选解满足包括问题规模在内的全部条件,因而是一

回溯法实验(最大团问题)

算法分析与设计实验报告第七次附加实验

} } 测试结果 当输入图如下时: 当输入图如下时: 1 2 3 4 5 1 2 3 4 5

当输入图如下时: 1 2 3 4 5

附录: 完整代码(回溯法) //最大团问题回溯法求解 #include using namespace std; class Clique { friend void MaxClique(int **,int *,int ); private: void Backtrack(int i); int **a; //图的邻接矩阵 int n; //图的顶点数 int *x; //当前解 int *bestx; //当前最优解 int cn; //当前顶点数 int bestn; //当前最大顶点数 }; void Clique::Backtrack(int i) { //计算最大团 if(i>n) //到达叶子节点 { for(int j=1;j<=n;j++) bestx[j]=x[j]; bestn=cn;

cout<<"最大团:("; for(int i=1;i=bestn) { //修改一下上界函数的条件,可以得到 x[i]=0; //相同点数时的解 Backtrack(i+1); } } void MaxClique(int **a,int *v,int n) { //初始化Y Clique Y; Y.x=new int[n+1]; Y.a=a; Y.n=n; https://www.360docs.net/doc/5e4618891.html,=0; Y.bestn=0; Y.bestx=v; Y.Backtrack(1); delete [] Y.x; cout<<"最大团的顶点数:"<

算法分析作业

算法分析练习题(一) 一、选择题 1、二分搜索算法是利用( A )实现的算法。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 2、下列不是动态规划算法基本步骤的是( A )。 A、找出最优解的性质 B、构造最优解 C、算出最优解 D、定义最优解 3.下列算法中通常以自底向上的方式求解最优解的是( B )。 A、备忘录法 B、动态规划法 C、贪心法 D、回溯法 4、衡量一个算法好坏的标准是(C )。 A 运行速度快 B 占用空间少 C 时间复杂度低 D 代码短 5、以下不可以使用分治法求解的是(D )。 A 棋盘覆盖问题 B 选择问题 C 归并排序 D 0/1背包问题 6. 实现循环赛日程表利用的算法是( A )。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 7.备忘录方法是那种算法的变形。( B ) A、分治法 B、动态规划法 C、贪心法 D、回溯法8.最长公共子序列算法利用的算法是( B )。 A、分支界限法 B、动态规划法 C、贪心法 D、回溯法9.实现棋盘覆盖算法利用的算法是( A )。 A、分治法 B、动态规划法 C、贪心法 D、回溯法 10. 矩阵连乘问题的算法可由(B)设计实现。 A、分支界限算法 B、动态规划算法 C、贪心算法 D、回溯算法 11、Strassen矩阵乘法是利用( A )实现的算法。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 12、使用分治法求解不需要满足的条件是(A )。 A 子问题必须是一样的 B 子问题不能够重复

C 子问题的解可以合并 D 原问题和子问题使用相同的方法解 13、下列算法中不能解决0/1背包问题的是(A ) A 贪心法 B 动态规划 C 回溯法 D 分支限界法 14.实现合并排序利用的算法是( A )。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法15.下列是动态规划算法基本要素的是( D )。 A、定义最优解 B、构造最优解 C、算出最优解 D、子问题重叠性质 16.下列算法中通常以自底向下的方式求解最优解的是( B )。 A、分治法 B、动态规划法 C、贪心法 D、回溯法 17、合并排序算法是利用( A )实现的算法。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 18.实现大整数的乘法是利用的算法( C )。 A、贪心法 B、动态规划法 C、分治策略 D、回溯法 19. 实现最大子段和利用的算法是( B )。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 20. 一个问题可用动态规划算法或贪心算法求解的关键特征是问题的( B )。 A、重叠子问题 B、最优子结构性质 C、贪心选择性质 D、定义最优解 21. 实现最长公共子序列利用的算法是( B )。 A、分治策略 B、动态规划法 C、贪心法 D、回溯法 二、填空题 1.算法的复杂性有时间复杂性和空间复杂性之分。 2、程序是算法用某种程序设计语言的具体实现。 3、算法的“确定性”指的是组成算法的每条指令是清晰的,无歧义的。 4.矩阵连乘问题的算法可由动态规划设计实现。 5、算法是指解决问题的一种方法或一个过程。

回溯法实验报告

实验04 回溯法 班级:0920561 姓名:宋建俭学号:20 一、实验目的 1.掌握回溯法的基本思想。 2.掌握回溯法中问题的解空间、解向量、显式约束条件、隐式约束条件以及子 集树与排列树的递归算法结构等内容。 3.掌握回溯法求解具体问题的方法。 二、实验要求 1.认真阅读算法设计教材,了解回溯法思想及方法; 2.设计用回溯算法求解装载问题、n后问题、图的m着色问题的java程序 三、实验内容 1.有一批共n个集装箱要装上2艘载重量分别为C1和C2的轮船,其中集装箱 i的重量为wi,且∑wi≤C1+C2。装载问题要求确定是否有一个合理的装载方案可将这个集装箱装上这2艘轮船。如果有,找出一种装载方案。 2.在n×n格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则, 皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于在n×n格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。 3.给定无向连通图G和m种不同的颜色。用这些颜色为图G的各顶点着色,每 个顶点着一种颜色。是否有一种着色法使G中每条边的2个顶点着不同颜色。 这个问题是图的m可着色判定问题。 四、算法原理 1、装载问题 用回溯法解装载问题时,用子集树表示其解空间是最合适的。可行性约束可剪去不满足约束条件(w1x1+w2x2+…+wnxn)<=c1的子树。在子集树的第j+1层结点Z处,用cw记当前的装载重量,即cw=(w1x1+w2x2+…+wjxj),当cw>c1时,以结点Z为根的子树中所有结点都不满足约束条件,因而该子树中的解均为不可行解,故可将该子树剪去。 解装载问题的回溯法中,方法maxLoading返回不超过c的最大子集和,但未给出达到这个最大子集和的相应子集。 算法maxLoading调用递归方法backtrack(1)实现回溯搜索。Backtrack(i)搜索

算法设计与分析:回溯法-实验报告

应用数学学院信息安全专业班学号姓名 实验题目回溯算法 实验评分表

实验报告 一、实验目的与要求 1、理解回溯算法的基本思想; 2、掌握回溯算法求解问题的基本步骤; 3、了解回溯算法效率的分析方法。 二、实验内容 【实验内容】 最小重量机器设计问题:设某一个机器有n个部件组成,每个部件都可以m个不同供应商处购买,假设已知表示从j个供应商购买第i个部件的重量,表示从j个供应商购买第i个部件的价格,试用回溯法求出一个或多个总价格不超过c且重量最小的机器部件购买方案。 【回溯法解题步骤】 1、确定该问题的解向量及解空间树; 2、对解空间树进行深度优先搜索; 3、再根据约束条件(总价格不能超过c)和目标函数(机器重量最小)在搜索过程中剪去多余的分支。 4、达到叶结点时记录下当前最优解。 5、实验数据n,m, ] ][ [j i w,] ][ [j i c的值由自己假设。 三、算法思想和实现【实现代码】

【实验数据】 假设机器有3个部件,每个部件可由3个供应商提供(n=3,m=3)。总价不超过7(c<=7)。 部件重量表: 部件价格表: 【运行结果】

实验结果:选择供应商1的部件1、供应商1的部件2、供应商3的部件3,有最小重量机器的重量为4,总价钱为6。 四、问题与讨论 影响回溯法效率的因素有哪些? 答:影响回溯法效率的因素主要有以下这五点: 1、产生x[k]的时间; 2、满足显约束得x[k]值的个数; 3、计算约束函数constraint的时间; 4、计算上界函数bound的时间; 5、满足约束函数和上界函数约束的所有x[k]的个数。 五、总结 这次实验的内容都很有代表性,通过上机操作实践与对问题的思考,让我更深层地领悟到了回溯算法的思想。 回溯算法的基本思路并不难理解,简单来说就是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。回溯法的基本做法是深度优先搜索,是一种组织得井井

回溯法实验报告

数学与计算机学院实验报告 一、实验项目信息 项目名称:回溯法 实验时间: 2016/06/08 实验学时: 03 学时 实验地点:工科楼503 二、实验目的及要求 理解回溯法的深度优先搜索策略、 掌握用回溯法解题的算法框架、 掌握回溯法的设计策略 三、实验环境 计算机Ubuntu Kylin14.04 CodeBlock软件四、实验内容及实验步骤 排兵布阵问题 某游戏中,不同的兵种处在不同的地形上其攻击能力不一样,现有n个不同兵种的角色{1,2,...,n},需安排在某战区n个点上,角色i在j点上的攻击力为A ij。试设计一个布阵方案,使总的攻击力最大。 数据: 防卫点 角 色 1 2 3 4 5 1 2 3 4 5 回溯法: 程序: #include int position[10]; int a[10][10]; int check(int k){//每个节点检查的函数 int i; for(i=0;i=0) { sum=0; position[k]=position[k]+1; while(position[k]<=n)

if(check(k))break; else position[k]=position[k]+1; if(position[k]<=n && k==n-1) { for(i=0;i

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 copy 1.#include "stdafx.h" 2.#include https://www.360docs.net/doc/5e4618891.html,ing namespace std; 4. 5.class Flowshop 6.{ 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 26.inline void S &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)值如下:"<

回溯法及其应用

八皇后问题的基本策略及其应用 郭洋洋王刚李晴孙佳 (陕西师范大学计算机科学学院09级计算机科学与技术,西安,710062) 摘要:针对八皇后问题,本文采用回溯法,给出递归与非递归两种算法的设计与分析,并通过实验验证两种算法的性能,得出最佳的算法。 关键词:八皇后;回溯法;递归算法;非递归算法 The Basic Algorithm Strategy For Eight Queens And Its Application Guo Yangyang,Wang Gang,Li Qing, Sun Jia (School of Computer Science ,Shanxi Normal University ,Xi’an ,710062 ) Abstract: Aiming at the problem of Eight Queens,this paper gives Backtracking , and Recursive algorithm and Non-recursive algorithm , and show the design and analysis of the two kinds of algorithms,and through the experiment ,verified the performance of them,getting the most suitable algorithm. Keywords: Eight Queens; Backtracking; Recursive; Non-Recursive 1 引言 八皇后问题由19 世纪著名的数学家高斯在1850 年提出:在8 ×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法. 回溯算法是尝试搜索算法中最为基本的一种算法,其采用了一种“走不通就调头”的思想,作为其控制结构[1]。回溯法在用来求问题的所有解时,要回溯到根,且根节点的所有可行的子树都已被搜索遍才结束。而回溯法在用来求解问题任一解时,只要搜索到问题的一个解就可以结束。这就是以深度优先的方式系统的搜索问题解的回溯法,它适用于解决一些类似n皇后问题等就切方案问题,也可以解决一些最优化问题。 2 问题描述与模型 八皇后问题:在8 ×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法. 例如图一所示:

算法分析与设计实验四回溯法

实验四 回溯法 实验目的 1. 掌握回溯法的基本思想方法; 2. 了解适用于用回溯法求解的问题类型,并能设计相应回溯法算法; 3. 掌握回溯法算法复杂性分析方法,分析问题复杂性。 预习与实验要求 1. 预习实验指导书及教材的有关内容,掌握回溯法的基本思想; 2. 严格按照实验内容进行实验,培养良好的算法设计和编程的习惯; 3. 认真听讲,服从安排,独立思考并完成实验。 实验设备与器材 硬件:PC 机 软件:C++或Java 等编程环境 实验原理 回溯法是最常用的解题方法,有“通用的解题法”之称。当要解决的问题有若干可行解时,则可以在包含问题所有解的空间树中,按深度优先的策略,从根节点出发搜索解空间树。算法搜索至解空间树的任一结点时,总是先判断该结点是否肯定不包含问题的解。如果肯定不包含,则跳过对以该结点为根的子树的搜索,继续查找该结点的兄弟结点,若它的兄弟结点都不包含问题的解,则返回其父结点——这个步骤称为回溯。否则进入一个可能包含解的子树,继续按深度优先的策略进行搜索。这种以深度优先的方式搜索问题的解的算法称为回溯法。它本质上是一种穷举法,但由于在搜索过程中不断略过某些显然不合适的子树,所以搜索的空间大大少于一般的穷举,故它适用于解一些组合数较大的问题。 回溯法也可以形式化地描述如下:假设能够用n 元组()n i x x x x ,,,,,21 表示一个给定问题P 的解,其中i i S x ∈。如果n 元组的子组()i x x x ,,,21 ()n i <满足一定的约束条件,则称为部分解。如果它已经是满足约束条件的部分解,则添加11++∈i i S x 形成新的子组()121,,,,+i i x x x x ,并检查它是否满足约束条件,若仍满足则继续添加22++∈i i S x ,并以此类推。如果所有的11++∈i i S x 都不满足约束条件,那么去掉1+i x ,回溯到i x 的位置,并去掉当前的i x ,另选一个i i S x ∈',组成新的子组()i x x x ',,,21 ,并判断其是否满足约束条件。如此反复下去,直到得到解或者证明无解为止。

回溯法

第8章回溯法 (1) 8.1概述 (1) 8.1.1 问题的解空间树 (1) 8.1.2 回溯法的设计思想 (2) 8.1.3 回溯法的时间性能 (3) 8.1.4 一个简单的例子——素数环问题 (4) 8.2图问题中的回溯法 (5) 8.2.1 图着色问题 (5) 8.2.2 哈密顿回路问题 (8) 8.3组合问题中的回溯法 (10) 8.3.1 八皇后问题 (10) 8.3.2 批处理作业调度问题 (13) 习题8 (16)

第8章回溯法 教学重点回溯法的设计思想;各种经典问题的回溯思想教学难点批处理作业调度问题的回溯算法 教学内容 和 教学目标 知识点 教学要求 了解理解掌握熟练掌握问题的解空间树√ 回溯法的设计思想√ 回溯法的时间性能√ 图着色问题√ 哈密顿回路问题√ 八皇后问题√ 批处理作业调度问题√ 8.1 概述 回溯法(back track method)在包含问题的所有可能解的解空间树中,从根结点出发,按照深度优先的策略进行搜索,对于解空间树的某个结点,如果该结点满足问题的约束条件,则进入该子树继续进行搜索,否则将以该结点为根结点的子树进行剪枝。回溯法常常可以避免搜索所有的可能解,所以,适用于求解组合数较大的问题。 8.1.1 问题的解空间树 复杂问题常常有很多的可能解,这些可能解构成了问题的解空间(solution space),并且可能解的表示方式隐含了解空间及其大小。用回溯法求解一个具有n个输入的问题,一般情况下,将问题的可能解表示为满足某个约束条件的等长向量X=(x1, x2, …, x n),其中分量x i(1≤i≤n)的取值范围是某个有限集合S i={a i,1, a i,2, …, a i,r i },所有可能的解向量构成了问题的解空间。例如,对于有n个物品的0/1背包问题,其可能解由一个等长向量{x1, x2, …, x n}组成,其中x i=1(1≤i≤n)表示物品i装入背包,x i=0表示物品i没有装入背包,则解空间由长度为n的0/1向量组成。当n=3时,其解空间是:

回溯法实验(最优装载)

算法分析与设计实验报告第二次附加实验 )用可行性约束函数可剪去不满足约束条件

附录: 完整代码(贪心法) //回溯法递归求最优装载问题#include #include #include using namespace std; template class Loading { public: void Backtrack(int i);

int n, //集装箱数 *x, //当前解 *bestx; //当前最优解 Type *w, //集装箱重量数组 c, //第一艘轮船的载重量 cw, //当前载重量 bestw, //当前最优载重量 r; //剩余集装箱重量 }; template void Loading::Backtrack(int i); template //参数为:w[]各物品重量数组,c为第一艘轮船的载重量,n为物品数量,bestx[]数组为最优解 Type MaxLoading(Type w[],Type c,int n,int bestx[]); int main() { int n=3,m; int c=50,c2=50; int w[4]={0,10,40,40}; int bestx[4]; clock_t start,end,over; //计算程序运行时间的算法 start=clock(); end=clock(); over=end-start; start=clock(); m=MaxLoading(w,c,n,bestx); //调用MaxLoading函数 cout<<"轮船的载重量分别是:"<

相关主题
相关文档
最新文档