pascal教程1回溯法.docx
《pascal教程1回溯法.docx》由会员分享,可在线阅读,更多相关《pascal教程1回溯法.docx(31页珍藏版)》请在冰豆网上搜索。
pascal教程1回溯法
第一章回溯法
1.1过河卒
源程序名knight.?
?
?
(pas,c,cpp)
可执行文件名knight.exe
输入文件名knight.in
输出文件名knight.out
【问题描述】
棋盘上A点有一个过河卒,需要走到目标B点。
卒行走的规则:
可以向下、或者向右。
同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。
因此称之为“马拦过河卒”。
棋盘用坐标表示,A点(0,0)、B点(n,m)(n,m为不超过15的整数),同样马的位置坐标是需要给出的。
现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。
【输入】
一行四个数据,分别表示B点坐标和马的坐标。
【输出】
一个数据,表示所有的路径条数。
【样例】
knight.inknight.out
66336
【算法分析】
从起点开始往下走(只有两个方向可以走),如果某个方向可以走再继续下一步,直到终点,此时计数。
最后输出所有的路径数。
这种方法可以找出所有可能走法,如果要输出这些走法的话这种方法最合适了,但是本题只要求输出总的路径的条数,当棋盘比较大时,本程序执行会超时,此时最好能找出相应的递推公式更合适,详见后面的递推章节。
1.2出栈序列统计
源程序名stack1.?
?
?
(pas,c,cpp)
可执行文件名stack1.exe
输入文件名stack1.in
输出文件名stack1.out
【问题描述】
栈是常用的一种数据结构,有n令元素在栈顶端一侧等待进栈,栈顶端另一侧是出栈序列。
你已经知道栈的操作有两·种:
push和pop,前者是将一个元素进栈,后者是将栈顶元素弹出。
现在要使用这两种操作,由一个操作序列可以得到一系列的输出序列。
请你编程求出对于给定的n,计算并输出由操作数序列1,2,…,n,经过一系列操作可能得到的输出序列总数。
【输入】
一个整数n(1<=n<=15)
【输出】
一个整数,即可能输出序列的总数目。
【样例】
stack1.instack1.out
35
【算法分析】
先了解栈的两种基本操作,进栈push就是将元素放入栈顶,栈顶指针上移一位,等待进栈队列也上移一位,出栈pop是将栈顶元素弹出,同时栈顶指针下移一位。
用一个过程采模拟进出栈的过程,可以通过循环加递归来实现回溯:
重复这样的过程,如果可以进栈则进一个元素,如果可以出栈则出一个元素。
就这样一个一个地试探下去,当出栈元素个数达到n时就计数一次(这也是递归调用结束的条件)。
1.3算24点
源程序名point24.?
?
?
(pas,c,cpp)
可执行文件名point24.exe
输入文件名point24.in
输出文件名point24.out
【问题描述】
几十年前全世界就流行一种数字游戏,至今仍有人乐此不疲.在中国我们把这种游戏称为“算24点”。
您作为游戏者将得到4个1~9之间的自然数作为操作数,而您的任务是对这4个操作数进行适当的算术运算,要求运算结果等于24。
您可以使用的运算只有:
+,-,*,/,您还可以使用()来改变运算顺序。
注意:
所有的中间结果须是整数,所以一些除法运算是不允许的(例如,(2*2)/4是合法的,2*(2/4)是不合法的)。
下面我们给出一个游戏的具体例子:
若给出的4个操作数是:
1、2、3、7,则一种可能的解答是1+2+3*7=24。
【输入】
只有一行,四个1到9之间的自然数。
【输出】
如果有解的话,只要输出一个解,输出的是三行数据,分别表示运算的步骤。
其中第一行是输入的两个数和一个运算符和运算后的结果,第二行是第一行的结果和一个输入的数据、运算符、运算后的结果;第三行是第二行的结果和输入的一个数、运算符和“=24”。
如果两个操作数有大小的话则先输出大的。
如果没有解则输出“Noanswer!
”
【样例】
point24.inpoint24.out
12372+1=3
7*3=21
21+3=24
【算法分析】
计算24点主要应用四种运算.开始状态有四个操作数,一个运算符对应两个操作数,所以一开始选择两个操作数分别对四个操作符进行循环检测,每一次运算后产生了新的数,两个数运算变成一个数,整体是少了一个操作数,所以四个数最终是三次运算。
由于操作的层数比较少(只有三层),所以可以用回溯的算法来进行检测,当找到一个解时便结束查找。
如果所有的情况都找过后仍然没有,则输出无解的信息。
1.4冗余依赖
源程序名redund.?
?
?
(pas,c,cpp)
可执行文件名redund.exe
输入文件名redund.in
输出文件名redund.out
【问题描述】
在设计关系数据库的表格时,术语“函数依赖”(FD)被用来表示不同域之间的关系。
函数依赖是描述一个集合中的域的值与另一个集合中的域的值之间的关系。
记号X->Y被用来表示当集合X中的域被赋值后,集合Y的域就可以确定相应的值。
例如,一个数据表格包含“社会治安编号”(S)、“姓名”(N)、“地址”(A)、“电话”(P)的域,并且每个人都与某个特定的互不相同的S值相对应,根据域S就可以确定域N、A、P的值。
这就记作S->NAP。
写一个程序以找出一组依赖中所有的冗余依赖。
一个依赖是冗余的是指它可以通过组里的其他依赖得到。
例如,如果组里包括依赖A->B、B->C和A->C,那么第三个依赖是冗余的,因为域C可以用前两个依赖得到(域A确定了域B的值,同样域B确定了域C的值)。
在A->B、B->C、C->A、A->C、C->B和B->A中,所有的依赖都是冗余的。
现在要求你编写一个程序,从给定的依赖关系中找出冗余的。
【输入】
输A的文件第一行是一个不超过100的整数n,它表示文件中函数依赖的个数。
从第二行起每一行是一个函数依赖且互不重复,每行包含用字符“-”和“>”隔开的非空域列表。
列表月包含大写的字母,函数依赖的数据行中不包括空格和制表符,不会出现“平凡”冗余依赖(如A->A)。
虽然文件中没有对函数依赖编号,但其顺序就是编号1到n。
【输出】
每一个冗余依赖,以及其他依赖的一个序列以说明该依赖是冗余的,先是一个FD,然后是依赖函数号,接着是"isredundantusingFDs:
”最后是说明的序列号。
如果许多函数依赖的序列都能被用来说明一个依赖是冗余的,则输出其中最短的证明序列。
如果这些函数依赖中不包含冗余依赖,则输出“NoredundantFDs”信息。
【样例1】【样例2】
redund.inredund.outredund.inredund.out
3FD3isredundantusingFDs:
126FD3isredundantusingFDs:
1
A->BD{即C可以通过1、2得到}P->RSTFD5isredundantusingFDs:
462
BD->CVRT->SQP
A->CPS->T
Q->TR
QS->P
SR->V
【算法分析】
一个依赖冗余,就是说它可以由其他依赖推导出来。
如何判断一个依赖能否被其他依赖推导出来呢?
假设判断的依赖为“a->b”,先找出所有形式为“a->*”的依赖,再由*开始找相关依赖,直到出现“#->b”为止(这里a、b、*、#都表示任意一个域名)。
如何实现这种查找呢?
可以通过筒单的回溯来完成。
只是找出冗余(如果有的话)还不说明工作就已结束,要到找出所有的能够推导出b的依赖序列,选出其中长度最短的(最短的也可能并不惟一,因此本题答案有可能并不惟一),最短的证明序列中一定不存在多余的依赖,如果不是最短的证明序列,那么该证明序列中就有可能还有冗余依赖。
1.5走迷宫
源程序名maze.?
?
?
(pas,c,cpp)
可执行文件名maze.exe
输入文件名maze.in
输出文件名maze.out
【问题描述】
有一个m*n格的迷宫(表示有m行、n列),其中有可走的也有不可走的,如果用1表示可以走,0表示不可以走,文件读入这m*n个数据和起始点、结束点(起始点和结束点都是用两个数据来描述的,分别表示这个点的行号和列号)。
现在要你编程找出所有可行的道路,要求所走的路中没有重复的点,走时只能是上下左右四个方向。
如果一条路都不可行,则输出相应信息(用-l表示无路)。
【输入】
第一行是两个数m,n(1【输出】
所有可行的路径,描述一个点时用(x,y)的形式,除开始点外,其他的都要用“一>”表示方向。
如果没有一条可行的路则输出-1。
【样例】
maze.in
56
100101
111111
001110
111110
111011
11
56
maze.out
(1,2)->(2,1)->(2,2)->(2,3)->(2,4)->(2,5)->(3,5)->(3,4)->(3,3)->(4,3)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(2,5)->(3,5)->(3,4)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(2,5)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(3,4)->(3,3)->(4,3)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(3,4)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(2,4)->(3,4)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,4)->(2,4)->(2,5)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,4)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(3,4)->(4,4)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(4,3)->(4,4)->(3,4)->(2,4)->(2,5)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(4,3)->(4,4)->(3,4)->(3,5)->(4,5)->(5,5)->(5,6)
(1,1)->(2,1)->(2,2)->(2,3)->(3,3)->(4,3)->(4,4)->(4,5)->(5,5)->(5,6)
【算法分析】
用一个a数组来存放迷宫可走的情况,另外用一个数组b来存放哪些点走过了。
每个点用两个数字来描述,一个表示行号,另一个表示列号。
对于某一个点(x,y),四个可能走的方向的点描述如下表:
1
4
x,y
2
3
对应的位置为:
(x-1,y),(x,y+1),(x+1,y),(x,y-1)。
所以每个点都要试探四个方向,如果没有走过(数组b相应的点的值为0)且可以走(数组a相应点的值为1)同时不越界,就走过去,再看有没有到达终点,到了终点则输出所走的路,否则继续走下去。
这个查找过程用search来描述如下:
proceduresearch(x,y,b,p);{x,y表示某一个点,b是已经过的点的情况,p是已走过的路}
begin
fori:
=1to4do{分别对4个点进行试探}
begin
先记住当前点的位置,已走过的情况和走过的路;
如果第i个点(xl,y1)可以走,则走过去;
如果已达终点,则输出所走的路径并置有路可走的信息,
否则继续从新的点往下查找search(xl,y1,b1,p1);
end;
end;
【思考与提高】
该程序通过引进新的变量和数组来继续新的查找,如果不引进新的变量和数组,那么每一次返回时要将原来的值还原才行,如果那样,程序应如何修改?
其实那样更加符合回溯的思想——换条路再试。
这类问题也可以归为搜索的问题,如果m和n的值相对比较大,则解可能很多,若题目只要找到一条路就结束程序时,在程序的输出部分后面加上一个halt就行了。
有些情况很明显是无解的,如从起点到终点的矩形中有一行或一列都是为0的,明显道路不通,对于这种情况要很快地“剪掉”多余分枝得出结论,这就是搜索里所说的“剪枝”。
从起点开始往下的一层层的结点,看起来如同树枝一样,对于其中的“枯枝”——明显无用的节点可以先行“剪掉”,从而提高搜索速度。
1.6单向双轨道
源程序名track.?
?
?
(pas,c,cpp)
可执行文件名track.exe
输入文件名track.in
输出文件名track.out
【问题描述】
如图所示l,某火车站有B,C两个调度站,左边入口A处有n辆火车等待进站(从左到右以a、b、c、d编号),右边是出口D,规定在这一段,火车从A进入经过B、C只能从左向右单向开,并且B、C调度站不限定所能停放的车辆数。
从文件输入n及n个小写字母的一个排列,该排列表示火车在出口D处形成的从左到右的火车编号序列。
输出为一系列操作过程,每一行形如“hLR”的字母序列,其中h为火车编号,L为h车原先所在位置(位置都以A、B、C、D表示),R为新位置。
或者输出‘NO’表示不能完成这样的调度。
【输入】
一个数n(1【输出】
可以调度则输出最短的调度序列,不可以调度时则输出‘NO’。
【样例】
track.intrack.out
3cAB
cbabAC
aAD
bCD
cBD
【算法分析】
这是一道类似于栈的操作题目,只不过是有两个栈B和C可以操作,而对于A序列里的元素,既可以进入B,也可以进入C,或直接到D。
解决问题可以从D序列出发反过来看,当前要到D的字符在哪里,如果在A,再看它前面有没有字符,如果有,先让它们进栈(B或C),否则直接到D;如果在B,看是否是栈顶元素,如果是,直接到D,否则将上面的字符进C;如果在C,看是否是栈顶元素,如果是,直接到D,否则无法进行操作,因为只能向右不能向左,这时要回溯。
如果所有的情况都检测过,某个字符不能进行到D的操作,则输出无解信息。
由于A里的非直接进入D的字符可以进入B或C,可以跟二进制建立起对应关系,将所有情况列举一遍,再找出其中步骤最少的输出。
1.7组合的输出
源程序名track.?
?
?
(pas,c,cpp)
可执行文件名track.exe
输入文件名track.in
输出文件名track.out
【问题描述】
排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r<=n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。
现要求你不用递归的方法输出所有组合。
例如n=5,r=3,所有组合为:
l23l24125l34l35145234235245345
【输入】
一行两个自然数n、r(1【输出】
所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。
【样例】
compages.incompages.out
53123
124
125
134
135
145
234
235
245
345
【算法分析】
如果通过循环加递归来实现回溯比较简单,相应程序为:
constmax=20;
vara:
array[0..max]ofinteger;
n,r:
1..max;
procedurecompages(k:
integer);{选取第k个元素}
vari,j:
integer;
begin
{当前所选元素最小为前一个元素加1,最大为n-(r-k),因为后面还有(r-k)个元素要选取,至少为每次选取留一个}
fori:
=a[k-1]+1ton-(r-k)dobegin
a[k]:
=i;{选取一个元素}
ifk=rthenbegin
forj:
=1tordowrite(a[j]:
3);
writeln;
end
elsecompages(k+1);
end;
end;
begin{main}
readln(n,r);
compages
(1);{从第一个元素开始选取给合数}
end.
本题要求不用递归来实现回溯,关键要定好回溯的条件。
如果选取第i个元素时选择了a[i],根据输出条件应有a[i]-i<=n-r,如果不满足这个条件说明当前第i个元素已再无数可取,要进行回溯,将其值减1,退到上一步将上一步原来的值再增加1,重复上述过程。
当全部选取完时,i回到了0,a[0]的值增加1后变为1,这是整个选取过程结束的条件。
1.8售货员的难题
源程序名salesman.?
?
?
(pas,c,cpp)
可执行文件名salesman.exe
输入文件名salesman.in
输出文件名salesman.out
【问题描述】
某乡有n个村庄(1为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为1,他不知道选择什么样的路线才能使所走的路程最短。
请你帮他选择一条最短的路。
【输入】
村庄数n和各村之间的路程(均是整数)。
【输出】
最短的路程。
【样例】
salesman.insalesman.out
3{村庄数}3
02l{村庄1到各村的路程}
102{村庄2到各村的路程}
210{村庄3到各村的路程}
【算法分析】
题目给定的村庄数不多(≤40),所以可以用回溯的方法,从起点(第一个村庄)出发找出所有经过其他所有村庄的回路,计算其中的最短路程。
当村庄数n比较大时这种方法就不太适用了。
用一个过程road(step,line:
byte)来描述走的状况,其中step是当前已到村庄数、line是当前所在的村庄。
如果step=n,下面只能回起点了,直接看第line个村庄到第一个村庄的路程加上已走的总路程,如果比最小值还小则替换最小值(要保存路径的话也可保存,这是回溯算法的优点,考虑到达最小值的路径可能不止一条,不便于测试,题目没要求输出路径)。
如果step还小于n,那么将还没有到过的村庄一个一个地试过去,再调用下一步road(step+1,新到的村庄号)。
1.9驾车旅游
源程序名tour.?
?
?
(pas,c,cpp)
可执行文件名tour.exe
输入文件名tour.in
输出文件名tour.out
【问题描述】
如今许多普通百姓家有了私家车,一些人喜爱自己驾车从一个城市到另一个城市旅游。
自己驾车旅游时总会碰到加油和吃饭的问题,在出发之前,驾车人总要想方设法得到从一个城市到另一个城市路线上的加油站的列表,列表中包括了所有加油站的位置及其每升的油价(如3.25元/L)。
驾车者一般都有以下的习惯:
(1)除非汽车无法用油箱里的汽油达到下一个加油站或目的地,在油箱里还有不少于最大容量一半的汽油时,驾驶员从不在加油站停下来;
(2)在第一个停下的加油站总是将油箱加满;
(3)在加油站加油的同时,买快餐等吃的东西花去20元。
(4)从起始城市出发时油箱总是满的。
(5)加油站付钱总是精确到0.1元(四舍五入)。
(6)驾车者都知道自己的汽车每升汽油能够行驶的里程数。
现在要你帮忙做的就是编写一个程序,计算出驾车从一个城市到另一个城市的旅游在加油和吃饭方面最少的费用。
【输入】
第一行是一个实数,是从出发地到目的地的距离(单位:
km)。
第二行是三个实数和一个整数,其中第一个实数是汽车油箱的最大容量(单位:
I。
);第二个实数是汽车每升油能行驶的公里数;第三个实数是汽车在出发地加满油箱时的费用(单位元);一个整数是1到50间的数,表示从出发地到目的地线路上加油站的数目。
接下来n行都是两个实数,第一个数表示从出发地到某一个加油站的距离(单位:
km);第二个实数表示该加油站汽油的价格(单位:
元)。
数据项中的每个数据都是正确的,不需判错。
一条线路上的加油站根据其到出发地的距离递增排列并且都不会大于从出发地到目的地的距离。
【输出】
就一个数据,是精确到0.1元的最小的加油和吃饭费用
【样例】
tour.intour.out
600379.6
408.51283
2003.52
3503.45
500365
【算法分析】
驾车者从出发地出发后对于每个加油站都可能有两种操作,一是进去加油买食品,二是不进去继续前行(如果当前汽车的余油可以的话),这样有n个加油站最多可能有2n种选择。
由于加油站数目不太多,可以采用回溯的算法来解决问题。
从第一个加油站开始,依次选择所要停下的下一个加油站,从而找出总费用最少的方案,加油站数目最多为50,这样回溯不会进行得很深。
在选择下一个要停下的加油站时比较麻烦,不能完全一个一个地试过去,这样时间太长。
可以用这样的方法:
先找出第一个要停下的加油站,判断其后面的加油站是否可以到达,如果不可到达就必须在这里停下来加油;否则就找出可以到达但如果只用一半汽油则无法到达的所有加油站,依次进行停靠。
1.10关路灯
源程序名power.?
?
?
(pas,c,cpp)
可执行文件名power.exe
输入文件名power.in
输出文件名power.out
【问题描述】
某一村庄在一条路线上安装了n盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有