递推-递归-分治-回溯

递推-递归-分治-回溯
递推-递归-分治-回溯

递推算法

在程序编辑过程中,我们可能会遇到这样一类问题,出题者告诉你数列的前几个数,或通过计算机获取了数列的前几个数,要求编程者求出第N项数或所有的数列元素(如果可以枚举的话),或求前N项元素之和。这种从已知数据入手,寻找规则,推导出后面的数的算法,称这递推算法。

典型的递推算法的例子有整数的阶乘,1,2,6,24,120…,a[n]=a[n-1]*n(a[1]=1);前面学过的2n,a[n]=a[n-1]*2(a[1]=1),菲波拉契数列:1,2,3,5,8,13…,a[n]=a[n-1]+a[n-2](a[1]=1,a[2]=2)等等。

在处理递推问题时,我们有时遇到的递推关系是十分明显的,简单地写出递推关系式,就可以逐项递推,即由第i项推出第i+1项,我们称其为显示递推关系。但有的递推关系,要经过仔细观察,甚至要借助一些技巧,才能看出它们之间的关系,我们称其为隐式的递推关系。

下面我们来分析一些例题,掌握一些简单的递推关系。

例如阶梯问题:题目的意思是:有N级阶梯,人可以一步走上一级,也可以一步走两级,求人从阶梯底走到顶端可以有多少种不同的走法。

这是一个隐式的递推关系,如果编程者不能找出这个递推关系,可能就无法做出这题来。我们来分析一下:走上第一级的方法只有一种,走上第二级的方法却有两种(两次走一级或一次走两级),走上第三级的走法,应该是走上第一级的方法和走上第二级的走法之和(因从第一级和第二级,都可以经一步走至第三级),推广到走上第i级,是走上第i-1级的走法与走上第i-2级的走法之和。很明显,这是一个菲波拉契数列。到这里,读者应能很熟练地写出这个程序。在以后的程序习题中,我们可能还会遇到菲波拉契数列变形以后的结果:如f(i)=f(i-1)+2f(i-2),或f(i)=f(i-1)+f(i-2)+f(i-3)等。

我们再来分析一下尼科梅彻斯定理。定理内容是:任何一个整数的立方都可以写成一串连续的奇数和,如:43=13+15+17+19=64。

从键盘输入一个整数n,要求写出其相应的连续奇数。

我们不妨从简单入手,枚举几个较小的数据:

13=1

23=3+5

33=7+9+11

43=13+15+17+19

53=21+23+25+27+29

根据上面的例子,读者不难看出:

(1)输入为n时,输出应有n项。

(2)输入分别为1,2,3…时,则输出恰好为连续奇数,1,3,5,7,9,11…即下一行的首项比上一行的末项大2。

经上面的分析,原本看不出递推关系的问题,呈现出递推关系。在趣的是,这个例子的递推过程,可以有多种算法。

算法一:将所有奇数逐项例举出来,然后将其分段,即:

1; 3 5; 7 9 11; 13 15 17 19; 21…

1 2 3 4 5…

算法二、设输入为n时的输出第一项为a[n],则a[n]=a[n-1]-n+1;

于是我们推出首项后,则输出为a[n]+a[n]+2+…+a[n]+2(n-1)

算法三、进一步总结,不难得出,若输入为n时,首项a[n]=n2-n+1,其余同算法二。下面我们来分析两个与动物有关的趣题。

这个题目中,读者不难理解,如果狼每次都能走入一个以前没有到过的洞中,则无论兔子怎么躲藏,都不能幸免遇狼。若狼跳越若干次后,不仅走到了以前到过的洞中,而且要跳越的洞数又与以前的某次相同,则狼就会循环往复地在同一些洞中跳来跳去,成为小丑而无法跳入以前没有到过的洞中。这些洞恰恰就是兔子的安全洞穴。

基于以上的分析,兔子有安全洞穴的条件有三,一是狼经过若干次跳跃后,回到原来到过的洞中;二是同一洞历史上的某一次跳越的洞数与即将要跳的这一次跨越的洞数也相同;三是到此时为止,狼应有从未到过的洞穴。

基本的算法是:从1号洞开始,按11->32->63->104->45->106->……方式,由前一个状态推导出下一个状态,同时将每一个新状态与前面存贮下来的状态作比较,以便找到两个可以作结论的状态。即要么狼已走遍了所有兔子洞,要么狼卷入死循环,而有些洞无法进入。

那么,现在我们怎样来存贮这些状态呢?最简单的办法是计数。

我们对每个洞建立一个数组,用来记载这个洞狼是否到过,在这个洞中跳越洞数,以及

另一个趣题是猴子吃桃问题:山中住有五只猴。一天,老大看见一堆桃子,想把桃子据为已有,却担心让老二老三知道了说自己太贪心。于是将桃分成相等的两份,却发现剩余一个,于是,老大吃掉这一个以后,再带走这堆桃的二分之一。第二天,老二也看到了这堆桃,其想法和做法与老大一样,老三老四老五也和他们的兄长想到一块去了。结果等老五吃完一个,带走一半以后,这堆桃还剩余11个。请编程计算当初这堆桃共有多少个。

这个下题目明眼人一看便知,我们如果按兄弟吃桃搬桃的相反顺序倒推过去,就能知道当初桃子的总数。其递推的公式是a[n-1]=a[n]*2+1。递推的初始值是a[5]=11(又称边界条件),待求a[0]的值。相信阅读本书到了此处的读者,很容易就能写出正确的程序。作者在这里不过是想明确一下,递推算法不仅可以顺着推、也可逆着推的道理。

如果设输入数据为a[1]…a[n],输出结果为b[1]…b[n],则根据题意,我们应该不难得出下面的结论:

1) a[k]:=b[k-1]+b[k]+b[k+1];

2) a[1]和a[n]都会小于3;

0 b[1]=0 b[2]=0

3) a[1]= 1 则b[1]=1 b[2]=0或b[1]=0 b[2]:=1

2 b[1]=1 b[2]=1

有了上述三个结论后,程序算法便跃然纸上。我们先根据a[1]得到b[1]和b[2]的值,然后递推出b[3]到b[n]。

在递推过程中,若递推出某个值b[k]>1或b[k]<0,如果a[1]=1,则交换b[1]和b[2]的值,然后再重新递推,若a[1]=0,2或已交换b[1],b[2],则问题无解。

递推算法是一个高效的算法,其时间复杂度是O(n)的。不仅如此,它同时也是后面要学习的其它算法的基础,读者应该好好地把握它,多做这方面的习题积累经验。

递推算法的关键是认真分析题意,发现递推关系,正确写出递推公式,求得边界条件,然后用循环实现即可。

探究总结

1.递推算法的基本形式,是指编程者让计算机循环求得一序列数值,而后一项的计算结果是由上一项或多项推导出来的。有时,我们为求得一个数列的某一项,我们不得不从第一项开始,逐个计算前面每一项的值。虽然这样做的效率不很高,但它能帮助我们解决许多实际问题。

2.无论是顺着还是逆着递推,其关键是递推公式是否正确,边界条件是否正确。二者有一个出错。则所有递推结果将都是错误的。

总结提高

1、用一种长为2米,宽为1米的石板,给一条长为N米的长直道路铺面,石板可以竖放或横放,但不能裁剪,请求石板不同的铺设方法有多少种。例如N=3时的铺法有以下几种,输出结束为3。

2、某生产企业,一年十二个月的生产利润与投资的比例均已预算出来,现假设企业从年从投入N万元,且下个月总是用上个月的本与利均作投资,请你编程求企业年底的总收入和纯利润和是多少。例如,他们的一个预算如下:

递归算法

如果一个程序编程者不会使用递归与分治策略,那么其它的程序编辑技巧就无从谈起。那么什么是递归与分治呢?要给它们下个定义可不容易,我们还是采用本书的编写特色,用实例来说明问题。

以前我们要求一个整数的阶乘,是采用循环相乘的方法进行的,程序的主要部分如下:s:=1;

for I:=1 to n do s:=s*I;

从这个算法我们不难看出,阶乘的递推关系为a[n]=n*a[n-1]。边界条件a[1]=1。采用递归算法的程序如下:

若输入n=5,则上述程序的的执行过程描述如下:

jc(5)=5*jc(4)

=5*4*jc(3)=20*jc(4)

=5*4*3*jc(2)=60*jc(2)

=5*4*3*2*jc(1)=120*jc(1)

=5*4*3*2*1=120

函数在求值的过程中,不断调用自身,使数据规模逐渐缩小,直到边界条件有确定的值。程序中没有看到循环,但具备循环的作用。

接着我们再来分析一个简单的递归例题。

图所示的情况,则输入为3和4,输出为4,长方形沿刻度线划分成一大三小

四个正方形。

对于给定的长和宽a,b,编程求正方形的个数。程序及其说明如下:Array

若输入两数为5×7,则上述程序的执行过程描述如下:

zhen(5,7)=1+zhen(5,2)

=2+zhen(3,2)

=3+zhen(1,2)

=4+zhen(1,1)

=5

上述执行过程,计算机五次使用了自定义函数以获得最终解,我们称这个程序的递归深度为5。由于计算机在处理递归时,要用到一种被称之为栈的存贮方式来存贮中间结果,而栈的深度、内存容量是有限的。我们在解题时,如果让计算机处理的递归深度超过了限度,计算机就会有“栈溢出(error 202 stack overflow error)”的出错提示。

所以编程时,应尽可能将递归的深度缩小。例如正方形一题可做下面的两个地方的优化,

优化后的递归过程为:

zhen(3,4)=4 div 3+zhen(3,1)=4 div 3+3 div 1

zhen(5,7)=7 div 5+zhen(5,2)=7 div 5+5 div 2+zhen(2,1)=7 div 5+5 div 2+2 div 1

相应的递归深度变为2和3。改进前对应深度分别为4和5。若输入数据较大,这个效果

这个题目很容易让人想到,从顶点开始每次都选择左下或右下中两数中最大的一个往下走。这就是我们邓上要学习的所谓贪心算法。但读者看看上图中这样的一条路径:5,6,7,7,8,9,其和为42。但另一条路径是5,6,4,8,9,14,其和为46。

递归算法的最大特点,是程序形式简单,思路清晰,能将复杂问题简单化。很多程序算法书在讲到递归算法时,有一个典型例子就是“汉诺塔游戏”问题,请读者参看有关书籍。这个例子将递归算法的优越性淋离尽致地表现出来。

但递归算法也有一个致命的弱点。由于递归过程中,计算机要存贮大量的中间数据,既耗时,又耗时间。有时还会进行很多重复运算。

例如:我们用递归方式求菲波拉契数列:

令f(1)=1,f(2)=1,f(n)=f(n-1)+f(n-2)

写成递归程序,大致如下:

function fibnacci(n:integer);longint;

begin

if n<3 then begin

fibnacci:=1;exit;

end;

fibnacci:=fibnacci(n-1)+fibnacci(n-2);

end;

这个过程并不算复杂,但计算机运行时却并不简单。例如:我们要求fibnacci(8),计算过程描述如下,请读者自己分析重复计算的情况:

f(8)=f(7)+f(6)

=[f(6)+f(5)]+[f(5)+f(4)]

={[f(5)+f(4)]+[f(4)+f(3)]}+{ [f(4)+f(3)]+[f(3)+f(2)]}

=……

上面三个例子都是递归函数的例子,其实过程同样可以递归。我们将“数字三角形”的

递归过程改写如下:

相应主程序部分请读者自己修改。一定要去做,利用这个机会,进一步理解过程与函数的联系与区别。

读者读到此处,对递归有所了解。可能有人早就作出了结论,过程或函数调用自身,使问题规模逐渐缩小至边界条件的已知值的方法,就是递归。但这个说法,只知其一,不知其二。事实上,我们可以书写两个不同的过程或函数a和b,二者相互调用,这种你中有我、我中有你有做法,也可以递归。不过,这类程序通常会有一定的难度,留待读者以后有机会再来探究。

探究结论

1.递归程序通常要有一个用来递归调用,即调用自身的过程或函数。递归过程或函数都必须有一个姢出递归的条件。否则,递归也就和死循环一样不能终止,直到存贮空间使用殆尽,出现出错提示(error 202,stack overflow error)为止。

2.递归程序,总是从问题的当前状态出发,逐步缩小问题的规模,直到达到某个边界条件为止,所以书写递归程序时,必须知道问题的递推关系和边界条件。例如,求菲波拉契数列时,既要知道f(n)=f(n-1)+f(n-2)的递推关系式,还要知道f(1)和f(2)的值。

总结提高

用递归算法做下面的题。

1.问题描述:在n个人中选出m(m<=n)个组成一个球队的不同选择方法,我们称之为n取m的组合,记作C(n,m)。

任务:编程,对于给定的输入,输出不同的组合情况和组合数。

样例输入:n,m=5 3

样例输出:543

542

541

532

531

521

432

431

421

3 21

C(5,3)=10

2.菲波拉契数列。对于给定的f(1)=a,f(2)=b,编程输出菲波拉契数列的前20项。

条件约束0

输入样例:1 2 5

输出样例:1 2 3 5 8

递归与分治

在学习循环结构的时候,我们学习过三种排序算法:直接选择排序、冒泡排序和插入法排序。这三种排序算法均需用内外两层循环,导致算法的时间效率均是O(n2)的。今有了递归算法,我们可以编写一个更高效率的排序算法,也是一个最常用的排序算法----快速排序算法。请大家仔细体会其中的诀窍。

我们用一个实例来说明这个算法的基本思路。取下面一些待排序的数组元素:

35 72 13 22 76 10 89 26

第一步将第一个数35存贮到一个临时变量中,把a[1]的位置空出来。然后我们从数组尾向前逐个查找,找到一个比35大的元素,放到空位a[1]上,很明显,此处有资格的是89,则a[1]=89,而a[7]的位置又空了下来(交没有真的空下来,只是这个数应该被移走)。

x=35

89 72 13 22 76 10 __ 26

第二步,我们再找一个能弥补a[7]空位的数。我们从队首开始,找一个小于35的数,即13,此时,a[7]=13,a[3]变成空位。数组排列如下:

x=35

89 72 __ 22 76 10 13 26

第三步,又从队尾开始,找比35大的数弥补空位。数组变成下面的形式:

x=35

89 72 76 22 __ 10 13 26

第四步,从队首开始,找小于35的数……,结果为

x=35

89 72 76 __ 22 10 13 26

到此为止,请读者比划一下,应得到如下结论:

1、35已与数组中的其它元素都比过一次了。

2、空位前的元素均比35大,空位后面的元素都比35小,可见空位应该是35最后的排

序位置。

3、如果,我们将35前后的两组数看作一个小规模的数组,按上述方法继续找出各元素

的最终位置的话,排序就可完成。

请看下面的程序。

快速排序算法,先找到数组的第一个元素的最终排序位置,然后,以此为“中介元素”,将数组分为前后两个部分,继续查找两个分段中的“中间”元素,再次分段,直到分段中只有一个元素为止。此时每个元素均已找到自己的最终排序位置,达到排序的目的。

像这种将大问题不断细分成几个部分分别处理,以减少运行时间的方法,我们称之为分治方法。分治方法通常以递归算法为基础,所以常常把分治和递归算法放在一起讨论。但分治算法也不一定需要递归。例如,前面我们所学过的三阶魔方问题,就不是递归的例子。

我们再来分析下面这个递归分治的例子。

我们先来分析最简单的情况,即假定k=1,则棋盘为一个2×2的形式。此时,无论坏格是

四个格子中的哪一个,问题的解是唯一的。恰好用一块硬纸片将其三个好格子覆盖。这就得

到了问题的初始条件,即递归出口。

下面的问题是,我们如何将一个大的棋盘变成较小的棋盘呢?

如果我们将一个2k×2k的棋盘均分成四个小棋盘的话,则四个小正方形棋盘的边长则为

2k-1×2k-1。且这四个小棋盘肯定只有一个坏了一格,另外三个棋盘却一个十格也没有。那么这一个含坏格的棋盘则是原问题缩小规模以后的情况。而三个没有Array坏格的棋盘则转化成另一个问题。

我们能不能使另外三个子问题也保持原问题的属性不变

呢?如右图,我们若将第一块硬纸板放在右图所示的位置,此时

三块无坏格的小棋盘也变成了四个有“坏格”的了。使四个子问

题保持了原问题的特性不变。

于是我们照此办法,不断地将一个变四个,每次将三个不含

坏块的相邻格子合用一块硬纸板覆盖。直到子问题的边长为2时,

放下唯一的一块硬纸板就行了。

程序的递归过程描述如下:

探索结论

1.我们如果能够将待解决的一个问题转化成多个子问题,然后将子问题的解合成为

原问题的解的程序算法,我们称其为分治算法.

2.分治算法,总是将问题的整体进行分解,使其成为与原问题算法相同,而规模较

小的子问题,直到子问题变成有确定的解为止。

总结提高

1、问题描述:从2n个数组元素中,找出最大值和最小值。

算法提示:如果用顺序查找数组元素值的方法,得到最大值和最小值,是可行的,时间效率为O(n)的。但用分治算法效率更高。算法大致描述如下:

将数组分成两个相等的部分,则数组元素的最大(小)值为两个部分的最大(小)值。不断将数组细分成两部分,直到每个部分只有两个元素时,则大数为最大值,小数为最小值。

2、归并排序算法:我们用以前所学的简单排序算法,时间效率是o(n2)的。如果,我们先将一个待排序数组分成两等分后,分别排序,并将两排序部分合成,则总的时间效率应该是:n2/4+n2/4+n,很显然,只要n>2 ,n2/4+n2/4+n

回溯算法

这一节,我们来讨论回溯算法。这种算法有一种基本固定的模式,通过下面的例子,我们重点来总结这种算法的基本模式。

我们先用回溯算法来解上节书中的“数字三角形”问题。算法示意图如下:

“数字三角形回溯算法示意图”中各箭头上的数字标号代表程序执行时,累加数值时的顺序。我们通过这个图来理解回溯算法的基本过程。

计算机从顶点5开始,沿路线⑴得到5+6,沿路线⑵得5+6+4,沿路线⑶⑷⑸,得到目前路径和5+6+4+8+9+14。到此无法沿该方向继续前进,记下路径和作为当前的最大值,并沿路径⑹回溯,改道⑺。由于再次到达底边2,再次得到一路径和5+6+4+8+9+2。但因新的路径和比已获得的最大值小,故没有保存的必要。但若是新的路径和大于原来存贮的最大值,则用新的路径和替代而成为目前的最优值。于是沿路线⑻回溯。回溯到数9后,发现

该结点的左右分支均已经历,不得不沿路线⑼再次回溯。

以后的⑽到⒃步的回溯过程,与前面所述方式一致。但经路线⒄⒅后,路线⒆⒇分别与⑾⑿两步重合。对于回溯,这两步和以后的重合步骤,都是必要的,这也就是回溯算法效率不高的重要原因。

在上述算法中,我们明显地看出,回溯算法中有三个不同的执行方向。

1.从当前位置向纵深发展。如路线⑴⑵⑶⑶⑷⑸。

2.从当前位置无法向纵深发展时,回退至上一个位置。如⑹⑻⑼⒂等路线。

3.从退回点改道,寻求可纵深发展的另一条路径,如⑺⑽⒀等路线。

另外一个重要的问题是回溯的最终出向。当程序执行至右下角的数10时,会沿图中回退箭头所示,不断回退,直至三角形顶点后,没有回退位置时,程序终止。

以上算法的程序如下:

我们下面来分析用回溯法以枚举n个数中取r个数的组合(关于组合的知识,详见本书第八章排列与组合)的所有情况。

这里我们先以5取3的组合的例子,来分析回溯的过程:

开始,我们顺序选择1,2,3,构成一个组合。

以后将3改成4,5,又得到两种组合1,2,4和1,2,5.

由于最后一位数不能再修改了,只得修改倒数第二位Array数(回溯),即将2分别改为3,4,于是类上面的分析分

别得到组合1,3,4; 1,3,5;1,4,5。

倒数第二位又无法再改了,于是只得回溯到第一个

数,于是可改第一位数为2,3。则得相应的组合为2,3,

4;2,3,5; 2,4,5;3,4,5。

共得结果为(回溯过程如右图所示):

1,2,3

1,2,4

1,2,5

1,3,4

1,3,5

1,4,5

2,3,4

2,3,5

2,4,5

3,4,5

探究结论

1、回溯算法的一般解题过程为

赋初始值

repeat

能进则进;

不能进则改;

改不了则退;

until 回溯至起点,不能再进行。

输出结果。

这种算法虽然模式相对固定,但还需较多时间的探索,才能掌握其中的奥妙。

2、回溯算法并非一种高效算法,它常常要枚举很多可能或不可能的情形。所以在以后使用的

过程中,要分析问题的规模,以免程序运行时间太长。以后还要注意与递归算法的结合。总结提高

1、骑士的游历。中国象棋中的马,只能从下图中的位置走向周围有标志的八个点的位置。现

规定马初始时,居于一个n*m的棋盘的左下角,每步只能向上向右趟至它可以至的位置。

请用回溯算法,寻求马能跳到棋盘右上角的正确跳法。

例如n=5,m=5时的一种正确跳法是:

(1,1),(2,3),(3,5),(4,3),(5,5)。

2、在上题的基础上,对马的走向不加限制,但要求马

走遍整个棋盘,且每一点都不重复。求一种可能的方案。

输出时,输出马每一步所在的位置。例如下图是一种可

行的走法。

练习题

1、用递归算法将一个十进制正整数转化为相应的二进制数,或将一个二进制数转化为相应的

十进制数。

2、用递归算法输出一个数组元素的全部元素。

3、用递归算法将一个数组元素反序输出。

4、用1-8八个数字不重复地填入下图所示的图形中,要求相邻格子的两数不相邻,求出所有

的填法。例如左图是合适的填法,而右图不是。

右图中:2与3邻,8与7邻,7与6邻。事实上只要其中一对相邻,则不合法。

5、设有如下图所示的地图,每个区域代表一个省,区域中的数字代表省的编号,将每个省涂上红,蓝,白,黄四种颜色之一,使相邻的省份有不同的颜色。

6、输入一个代数表达式(只含有+,-,*,/,(,),1,2,3,4,5,6,7,8,9,0等字符,每个数字均小于10),设表达式除括号匹配有误外无其他错误.编程对输入的表达式检验,判断括号匹配是否正确.

7、若一个数(首位不为零)从左向右读与从右向左读都是一样,我们就将其称之为回

文数。例如:给定一个10进制数56,将56加65(即把56从右向左读),得到121是

一个回文数。又如,对于10进制数87,

STEPl:87+78= 165 STEP2:165+561= 726

STEP3:726+627=1353 STEP4:1353+3531=4884

在这里的一步是指进行了一次N进制的加法,上例最少用了4步得到回文数4884。

写一个程序,给定一个N(2<n<=10,N=16=进制数M.求最少经过几步可以得到回文数。如果在30步以内(包含30步)不可能得到回文数,则输出“Inpossible”

样例:

INPUT

N=9 M=87

Output

STEP=6

8、数的计数

[问题描述]

我们要求找出具有下列性质数的个数(包含输入的自然数n):先输入一个自然数n(n≤1000),然后对此自然数按照如下方法进行处理:

l)不作任何处理:

2)在它的左边加上一个自然数,但该自然数不能超过原数的一半;

3)加上数后,继续按此规则进行处理,直到不能再产生数为止。

[样例] 输入:6

满足条件的数为 6 (此部分不必输出)

16 26 126 36 136 输出:6

9、最大公约数与最小公倍数问题

[问题描述]

输入二个正整数x0,y0(2≤x0≤100000,2≤y0≤1000000),求出满足下列条件的P、Q的个数。

条件:1.P、Q是正整数

二要求P、Q以xO为最大公约数,以yO为最小公倍数。

试求,满足条件的所有可能的两个正整数的个数。

[样例]

输入:x0=3 y0=60

输出:4

说明:(不用输出)此时的 P Q 分别为,

3 60

15 12

12 15

60 3

所以,满足条件的所有可能的两个正整数的个数共4

10、级数求和

[问题描述]:

已知:Sn= 1+1/2+1/3+…+1/n。显然对于任意一个整数K,当n足够大的时候,Sn大于K。

现给出一个整数K(1<=k<=15),要求计算出一个最小的n;使得Sn>K。

[输入]

键盘输入 k

[输出]

屏幕输出 n

[输入输出样例]

输人:1

输出:2

11、选数(存盘名:NOIPC2)

[问题描述]:

已知 n 个整数x1,x2,…,xn,以及一个整数 k(k<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:

3+7+12=22 3+7+19=29 7+12+19=38 3+12+19=34。

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数:3+7+19=29)。

[输入]:

键盘输入,格式为:

n , k (1<=n<=20,k<n)

x1,x2,…,xn (1<=xi<=5000000)

[输出]:

屏幕输出,格式为:

一个整数(满足条件的种数)。

[输入输出样例]:

输入:

4 3

3 7 12 19

输出:

1

12、在N*N的棋盘上(1<=N<=10)填入1,2,...N*N共N*N个数,使得任意两个相邻的数之和为素数.例如,当N=2时,有

1 2

4 3

输入第一行为一整数T,表示有T组测试数据.

每组测试数据一行,为一整数N(1<=N<=10)

输出满足条件的最小序列的方案。

最小序列即将每一行连接起来组成一行,然后使前面的尽可能小,当第一个数字相同时则比较下面一个,依次类推。

比如当N=2时,序列为1 2 4 3,当无满足条件的方案时输出"NO"。

算法实验 递归回溯解八皇后问题

深圳大学实验报告 课程名称:算法分析与复杂性理论 实验项目名称:八皇后问题 学院:计算机与软件学院 专业:软件工程 指导教师:杨烜 报告人:学号:班级:15级软工学术型实验时间:2015-12-08 实验报告提交时间:2015-12-09 教务部制

一.实验目的 1.掌握选回溯法设计思想。 2.掌握八皇后问题的回溯法解法。 二.实验步骤与结果 实验总体思路: 根据实验要求,通过switch选择八皇后求解模块以及测试数据模块操作,其中八皇后模块调用摆放皇后函数模块,摆放皇后模块中调用判断模块。测试数据模块主要调用判断模块进行判断,完成测试。用一维数组保存每行摆放皇后的位置,根据回溯法的思想递归讨论该行的列位置上能否放置皇后,由判断函数Judge()判断,若不能放置则检查该行下一个位置。相应结果和过程如下所示(代码和结果如下图所示)。 回溯法的实现及实验结果: 1、判断函数 代码1: procedure BTrack_Queen(n) //如果一个皇后能放在第K行和X(k)列,则返回true,否则返回false。 global X(1:k);integer i,k i←1 while i0 do X(k)←X(k)+1 //移到下一个位置 while X(k)<=n and not Judge(k) do //判断能否放置皇后 X(k)←X(k)+1 repeat if X(k)<=n //找到一个位置 then if k=n //是一个完整的解吗

《递归算法与递归程序》教学设计

递归算法与递归程序 岳西中学:崔世义一、教学目标 1知识与技能 (1) ?认识递归现象。 (2) ?使用递归算法解决冋题往往能使算法的描述乘法而易于表达 (3) ?理解递归三要素:每次递归调用都要缩小规模;前次递归调用为后次作准备:递归调用必须有条件进行。 (4) ?认识递归算法往往不是咼效的算法。 (5) ? 了解递归现象的规律。 (6) ?能够设计递归程序解决适用于递归解决的问题。 (7) ?能够根据算法写出递归程序。 (8) ? 了解生活中的递归现象,领悟递归现象的既有重复,又有变化的特点,并且从中学习解决问题的一种方法。 2、方法与过程 本节让同学们玩汉诺塔的游戏,导入递归问题,从用普通程序解决斐波那契的兔子问题入手,引导学生用自定义了一个以递归方式解决的函数过程解决问题,同时让同学们做三个递归练习,巩固提高。然后让学生做练习(2) 和练习(3)这两道题目的形式相差很远,但方法和答案却是完全相同的练习,体会其中的奥妙,加深对递归算法的了解。最后用子过程解决汉诺塔的经典问题。 3、情感态度和价值观 结合高中生想象具有较强的随意性、更富于现实性的身心发展特点,综合反映出递归算法的特点,以及递归算法解答某些实践问题通常得很简洁,从而激发学生对程序设计的追求和向往。 二、重点难点 1、教学重点 (1) 了解递归现象和递归算法的特点。 (2) 能够根据问题设计出恰当的递归程序。 2、教学难点 (1) 递归过程思路的建立。 (2) 判断冋题是否适于递归解法。 (3) 正确写出递归程序。 三、教学环境 1、教材处理 教材选自《浙江省普通高中信息技术选修:算法与程序设计》第五章,原教材的编排是以本节以斐波那契的兔子问题引人,导出递归算法,从而自 定义了一个以递归方式解决的函数过程。然后利用子过程解决汉诺塔的经典问题。 教材经处理后,让同学们玩汉诺塔的游戏,导入递归问题,从用普通程序解决斐波那契的兔子问题入手,引导学生用自定义了一个以递归方式解决的函数过程解决问题,同时让同学们做三个递归练习,巩固提高。然后让学生做练习⑵ 和练习

八皇后之递归算法、回溯算法、穷举算法

VAR CONT,I:INTEGER; A:ARRAY[1..N] OF BYTE;{存放正确的一组解} C:ARRAY[1..N] OF BOOLEAN;{存放某一列放皇后的情况,用于判断是否有同列的情况} L:ARRAY[1-N..N-1] OF BOOLEAN;{存放某一斜线上放皇后的情况,用于判断是否有同斜线的情况;斜线的方向为\} R:ARRAY[2..2*N] OF BOOLEAN;{存放某一斜线上放皇后的情况,用于判断是否有同斜线的情况;斜线的方向为/} PROCEDURE PR; VAR I:INTEGER; BEGIN FOR I:=1 TO N DO WRITE(A[I]:4); INC(CONT); WRITELN(' CONT=',CONT); END; PROCEDURE TRY(I:INTEGER); VAR J:INTEGER; PROCEDURE ERASE(I:INTEGER); BEGIN C[J]:=TRUE; L[I-J]:=TRUE; R[I+J]:=TRUE; END; BEGIN FOR J:=1 TO N DO

IF C[J] AND L[I-J] AND R[I+J] THEN BEGIN A[I]:=J; C[J]:=FALSE; L[I-J]:=FALSE; R[I+J]:=FALSE; IF I

回溯法与分支限界法的分析与比较

回溯法与分支限界法的分析与比较 摘要:通过对回溯法与分支限界法的简要介绍,进一步分析和比较这两种算法在求解问题时的差异,并通过具体的应用来说明两种算法的应用场景及侧重点。 关键词:回溯法分支限界法n后问题布线问题 1、引言 1.1回溯法 回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。这种以深度优先方式系统搜索问题解的算法称为回溯法。 1.2分支限界法 分支限界法是以广度优先或以最小耗费优先的方式搜索解空间树,在每一个活结点处,计算一个函数值,并根据函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间上有最优解的分支推进,以便尽快地找出一个最优解,这种方法称为分支限界法。 2、回溯法的基本思想 用回溯法解问题时,应明确定义问题的解空间。问题的解空间至少应包含问题的一个解。之后还应将解空间很好的组织起来,使得能用回溯法方便的搜索整个解空间。在组织解空间时常用到两种典型的解空间树,即子集树和排列树。确定了解空间的组织结构后,回溯法从开始结点出发,以深度优先方式搜索整个解空间。这个开始结点成为活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向纵深方向移动,则当前扩展结点就成为死结点。此时,应往回移动至最近的一个活结点处,并使这个活结点成为当前的扩展结点。回溯法以这种工作方式递归的在解空间中搜索,直至找到所要求的解或解空间中已无活结点时为止。 3、分支限界法的基本思想 用分支限界法解问题时,同样也应明确定义问题的解空间。之后还应将解空间很好的组织起来。分支限界法也有两种组织解空间的方法,即队列式分支限界法和优先队列式分支限界法。两者的区别在于:队列式分支限界法按照队列先进先出的原则选取下一个节点为扩展节点,而优先队列式分支限界法按照优先队列

递归方程求解方法综述

递归方程求解方法综述 摘要:随着计算机科学的逐步发展,各种各样的算法相继出现,我们需要对算法进行分析,以选择性能更好的解决方案。算法分析中计算复杂度常用递归方程来表达,因此递归方程的求解有助于分析算法设计的好坏。阐述了常用的3种求解递归方程的方法:递推法、特征方程法和生成函数法。这3种方法基本上可以解决一般规模递归方程的求解问题。 关键词:递归;递推法;特征方程;生成函数 0引言 寻求好的解决方案是算法分析的主要目的,问题的解决方案可能不只一个,好的方案应该执行时间最短,同时占有存储空间最小,故算法分析一般考虑时间复杂性、空间复杂性两方面的参数。在算法分析时我们采用时间耗费函数来表示时间参数,用当问题规模充分大时的时间耗费函数的极限表示时间复杂度。 一般算法对应的时间耗费函数常用递归方程表示,找出递归方程的解,就可以表示其对应算法复杂度的渐进阶,从而比较算法的优劣。因此研究递归方程的解法意义重大。下文将分析并给出常用递归方程的3种解法。 1递归方程的解法 递归方程是对实际问题求解的一种数学抽象,递归的本质在于将原始问题逐步划分成具有相同解题规律的子问题来解决,原始问题与子问题仅在规模上有大小区别,并且子问题的规模比原始问题的

规模要小。对于规模为n的原始问题,我们通常会寻找规模n的问题与规模n-1或者规模n/2的问题之间存在的联系,从而进一步推导出具有递归特性的运算模型。 根据递归方程的一般形式,常用的解法有三种,分别是递推法、公式法及生成函数法。下面就分别来分析其求解过程。 1.1递推法 当递归方程形式简单且阶数较低时,一般可以采用递推法求解,根据一步一步递推找到方程的递推规律,得到方程的解。下面举例说明: t(1)=0 t(n)=2t(n/2)+n2(n≥2) t(n)=2t(n/2)+n2=2(2t(n/22)+(n/2)2)+n2 =22t(n/2)2+2n2/22+n2 =22(2t(n/23)+(n/22)2)+2n2/22+n2 =23(2t(n/23)+22n2/(22)2)+2n2/(22)1+n2… =2kt(n/2k)+∑k-1i=02in2(22)i递推到这里我们就可以发现递 归规律,找到递归出口, t(1)=0,令n=2k 则可以得到如下结果:t(n) =2kt(1) +∑k-1i=0n2(1/2)i)= n2(1-(1/2)k1-1/2)=2n2-2n 上面得到方程的解,我们来分析其对应算法复杂性的渐进阶,根据渐进阶定理有:设有函数f(n),g(n)均是规模n的函数,则o(f(n))+o(g(n))=o(max(f(n), g(n)))。故有t(n)=o(n2)。 1.2公式法

关于递归与递推的那七道题

关于递归与递推的那七道题 递归与递推是动态规划最底层的东西,掌握好它对于彻底的理解动规是至关重要的,这次做的题不难,但是它很能锻练人的思维,每一道题都有多种解法。只要静下心来想,一般人都能做出来,而在做题的过程中,你会有很大的收获。 1、一只小蜜蜂... 题目是这样的,有一只经过训练的蜜蜂只能爬向右侧相邻的蜂房,不能反向爬行。请编程计算蜜蜂从蜂房a爬到蜂房b的可能路线数。 对于这种题目,我们首先得找出蜂房之间的关系,如从1只能走到2和3,从2只能走到3和4,从3只能走到4和5……,因此 我们可以得到它们的递推关系: f[a] = f[a+1]+f[a+2]; 下面我们再来找出口,把这个问题化简,即当a与b相邻时(a+1=b 或a+2=b),f[a] = 1,所以这个问题可以解决了。它就是一个递归,遇到b就结束。为了减少重复访问,我们可以用记忆化搜索来提高效率。 这个题也可以顺着来推,即从a到a有0条路线,从a到a+1有1条路线,从a 到a+2有1条路线,而a+3的路线条数来源于a+1 和a+2的路径条数。 f[a] = 0; f[a+1] = 1; f[a+2] = 1; f[a+3] = f[a+1]+f[a+2]; …… f[b] = f[b-1]+f[b-2]; 由上面的式子就可以看出,它就是一个菲波拉契数列。 2、LELE的RPG难题

题目描述:有排成一行的n个方格,用红(Red)、粉(Pink)、绿(Green)三色涂每个格子,每格涂一色,要求任何相邻的方格不能同色,且首尾两格也不同色.求全部的满足要求的涂法. 以上就是著名的RPG难题. 解法一: 这个题目经过同学们的讨论,得出了三种解法。我所用的方法还是搜索,我们先不考虑它的限制条件,把第一个格子看成是树的根,那么总共可以建成三棵树,每一棵树的结点都有三个孩子。我们的目标就是要统计这三棵树中分别从根结点走到叶子结点的总的路径条数。然后把限制条件加上,把不符合要求的路径去掉,即可得出最终的结果。具体的做法同样是记忆化搜索. 解法二:递推公式 f[n] = 3 * 2^(n-1) – f[n-1]; 解法三:数学公式 f[n] = (k-1)^n+(k-1)*(-1)^n; 证明略。 3、骨牌铺方格 题目描述:在2×n的一个长方形方格中,用一个1× 2的骨牌铺满方格,输入n ,输出铺放方案的总数. 法一: 这道题目,我是用数学方法解的,可以说是枚举。骨牌放入方格中只有两种方式,横放或竖放,设横放的有x张,竖放的有y张。则X+y = n;并且x只能是偶数(要把方格填满,横着放的骨牌只能是成对出现),而题目给出n最大为50,所x,y 完全可以很快的枚举 出来。剩下的工作只需对由x/2,y组成的多重集合进行排列就行了。这个集合中共有两类不同类型的元素,第一类元素的重数k1=x/2, 第二类元素的重数k2=y;故最后的结果为(k1+k2)!/(k1! * k2!)。 法二: 假设f[n]为铺放方案的总数,我们只考虑最后放的那几张骨牌,有两种可能,一种是只放一张,竖着放,此时的方案总数为f[n-1],还有 一种可能是放两张,横着放,此时的方案总数为f[n-2]。因此我可以得出递推公式 f[n] = f[n-1]+f[n-2]。 法三:

枚举-递归-回溯

一、暴力求解法(枚举法/ 穷举法) 概念:什么是枚举法? 在进行归纳推理时,如果逐个考察了某类事件的所有可能情况,因而得出一般结论,那么这结论是可靠的,这种归纳方法叫做枚举法。即,把所要解决问题的所有可能性都列举出来,一一试验。 枚举应用简单举例:求1~100之间的素数;求水仙花数;鸡兔同笼问题;百元买百鸡问题;整数(分数)拆分问题;排列问题…… 枚举算法因为要列举问题的所有可能的答案,所有它具备以下几个特点: 1、得到的结果肯定是正确的; 2、可能做了很多的无用功,浪费了宝贵的时间,效率低下。 3、通常会涉及到求极值(如最大,最小,最重等)。 4、数据量大的话,可能会造成时间崩溃。 采用枚举算法解题的基本思路: (1)确定枚举对象、枚举范围和判定条件; (2)一一枚举可能的解,验证是否是问题的解 下面我们就从枚举算法的的优化、枚举对象的选择以及判定条件的确定,这三个方面来探讨如何用枚举法解题。 例1:百元买百鸡问题:有一个人有一百块钱,打算买一百只鸡。到市场一看,大鸡三块钱一只,小鸡一块钱三只,不大不小的鸡两块钱一只。现在,请你编一程序,帮他计划一下,怎么样买法,才能刚好用一百块钱买一百只鸡? 算法分析: 我们以三种鸡的个数为枚举对象(分别设为x,y,z),以三种鸡的总数(x+y+z)和买鸡用去的钱的总数(x*3+y*2+z/3)为判定条件,穷举各种鸡的个数。 (1)基本算法: for (x=0;x<=100;x++) for (y=0;y<=100;y++) for(z=0;z<=100;z++) if(x+y+z==100 && z%3==0 && x*3+y*2+z/3==100)输出x,y,z (2)优化算法:只需要枚举2种鸡x(x<=33)和y(y<=50),第3种根据约束条件100-x-y可得:

递推与递归练习题

练习 1.(用递归做)5个人坐在一起,问第五个人多少岁?他说比第4个人大2岁。 问第4个人岁数,他说比第3个人大2岁。问第三个人,又说比第2人大两岁。问第2个人,说比第一个人大两岁。最后问第一个人,他说是10岁。请问第五个人多大? 程序分析:利用递归的方法,递归分为回推和递推两个阶段。要想知道第五个人岁数,需知道第四人的岁数,依次类推,推到第一人(10岁),再往回推。 2.(用递归做)商人渡河问题是这样的:有三个商人,三个强盗,和一条船(船 每次只可以载小于等于两个人)他们同在河的一边,想渡过河去,但是必须保证在河的任何一边必须保证商人的数目大于等于强盗的数目,应该怎么过这条河呢? 注意:开始时商人,强盗所在的河的这边设为0状态,另一边设为1状态(也就是船开始时的一边设为0,当船驶到对岸是设为1状态,在这两个状态时,都必须符合条件) 3.(用递推做)猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾, 又多吃了一个,第二天早上又将剩下的桃子吃掉一半,又多吃了一个。 以后每天早上都吃了前一天剩下的一半多一个。到第30天早上想再吃时,见只剩下1个桃子了。求第一天共摘了多少。 4.(用递推做)已知一对兔子每一个月能生一对小兔子,而一对小兔子出 生后第二个月就开始生小兔子,假如一年内没有发生死亡,则以对兔子一年能繁殖成多少对?

答案: 1.#include int age(int n) { if(n==1) return(10); else return age(n-1)+2; } void main() { int n; n=5; printf("The fifth age is %d.\n",age(n)); } 2.#include #include #include struct node /*建立一个类似栈的数据结构并且可以浏览每一个数据点*/ { int x; int y; int state; struct node *next; }; typedef struct node state; typedef state *link; link PPointer1=NULL; link PPointer2=NULL; int a1,b1; int a2,b2; /*栈中每个数据都分为0,1状态*/ void Push(int a,int b,int n) { link newnode; newnode=(link)malloc(sizeof(state)); newnode-> x=a; newnode-> y=b; newnode-> state=n; newnode-> next=NULL; if(PPointer1==NULL) { PPointer1=newnode; PPointer2=newnode; }

递推和递归作业

题1:编码(encode.???) 【问题描述】 编码工作常被运用于密文或压缩传输。这里我们用一种最简单的编码方式进行编码:把一些有规律的单词编成数字。 字母表中共有26个字母{a,b,……,z},这些特殊的单词长度不超过6且字母按升序排列。把所有这样的单词放在一起,按字典顺序排列,一个单词的编码就对应着它在字典中的位置。 例如: a --> 1 b --> 2 z --> 26 ab --> 27 ac --> 28 你的任务就是对于所给的单词,求出它的编码。 【输入格式】 仅一行,被编码的单词。 【输出格式】 仅一行,对应的编码。如果单词不在字母表中,输出0。 【输入样例】 ab 【输出样例】 27 题2:特殊的子集(subset.???) 【问题描述】 集合M={1,2,3,……n}的子集中,有一些是不含相邻自然数元素的。例如:n=4时,集合{1,3}是满足要求的,而{1,3,4}是不满足的,因为它含有相邻自然数3和4。把所有满足要求的子集记作S i,对于每一个S i计算出它的所有元素的乘积T i,求∑T i2。 【输入格式】 仅一行,包括一个正整数n(n≤100) 【输出格式】 仅一行,即T i的平方和,可能会超出长整型范围。 【输入样例】 4

【输出格式】 119 题3:素数环(prime.???) 【问题描述】 给定一个n,求1..n组成的环,使得环上相邻的元素和为素数。 【输入格式】 n(1<=n<=10) 【输出格式】 把1放在第一位置,按照字典顺序不重复的输出所有解(顺时针,逆时针算不同的两种),相邻两数之间严格用一个整数隔开,每一行的末尾不能有多余的空格。 【输入样例】 8 【输出样例】 1 2 3 8 5 6 7 4 1 2 5 8 3 4 7 6 1 4 7 6 5 8 3 2 1 6 7 4 3 8 5 2 题4:火力网(fire.???) 【问题描述】 在一个n*n的阵地中,有若干炮火不可摧毁的石墙,现在要在这个阵地中的空地上布置一些碉堡。假设碉堡只能向上下左右四个方向开火,由于建筑碉堡的材料挡不住炮火,所以任意一个碉堡都不能落在其它碉堡的火力范围内,请问至多可建造几座碉堡? 【输入格式】 第一行一个整数n(n<=10)。 下面n行每行为一个由n个字符构成的字符串,表示阵地的布局,包括空地('.'),和石墙('X')。 【输出格式】 一个整数,即最多可建造的碉堡数。 【输入样例】 4

回溯搜索算法

补充2 回溯法 解回溯法的深度优先搜索策略 z理解回溯法的深度优先搜索策略。 z掌握用回溯法解题的算法框架 (1)递归回溯 (2)迭代回溯 (3)子集树算法框架 (4)排列树算法框架 通过应用范例学习回溯法的设计策略 z通过应用范例学习回溯法的设计策略。

Sch2-1z Sch2-1 方法概述搜索算法介绍 (1)穷举搜索 (2)盲目搜索 —深度优先(DFS)或回溯搜索( Backtracking); —广度优先搜索( BFS ); (Branch &Bound) —分支限界法(Branch & Bound);—博弈树搜索( α-βSearch) (3)启发式搜索 —A* 算法和最佳优先( Best-First Search ) —迭代加深的A*算法 —B*AO*SSS*等算法B , AO , SSS 等算法 —Local Search, GA等算法

Sch2-1z Sch2-1 方法概述搜索空间的三种表示: —表序表示:搜索对象用线性表数据结构表示; —显示图表示:搜索对象在搜索前就用图(树)的数据结构表示; —隐式图表示:除了初始结点,其他结点在搜索过程中动态生成。缘于搜索空间大,难以全部存储。 z 搜索效率的思考:随机搜索 —上世纪70年代中期开始,国外一些学者致力于研究随机搜索求解困难的组合问题,将随机过程引入搜索; —选择规则是随机地从可选结点中取一个从而可以从统计角度分析搜选择规则是随机地从可选结点中取一个,从而可以从统计角度分析搜索的平均性能; —随机搜索的一个成功例子是:判定一个很大的数是不是素数,获得了第个多式时算法 第一个多项式时间的算法。

搜索与回溯算法介绍

搜索与回溯算法介绍 一、概述: 计算机常用算法大致有两大类:一类叫蛮干算法,一类叫贪心算法。前者常使用的手段就是搜索,对全部解空间进行地毯式搜索,直到找到指定解或最优解。后者在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干次的贪心选择,最终得出整个问题的最优解。 二、搜索与回溯: 这里着重介绍搜索与回溯。当很多问题无法根据某种确定的计算法则来求解时可以利用搜索与回溯的技术求解。回溯是搜索算法中既带有系统性又带有跳跃性的一种控制策略。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索。在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,然后继续向前探索,如此反复进行,直至得到解或证明无解。如迷宫问题:进入迷宫后,先随意选择一个前进方向,一步步向前试探前进。如果碰到死胡同,说明前进方向已无路可走,这时,首先看其它方向是否还有路可走,如果有路可走,则沿该方向再向前试探;如果已无路可走,则返回一步,再看其它方向是否还有路可走;如果有路可走,则沿该方向再向前试探。按此原则不断搜索回溯再搜索,直到找到新的出路或从原路返回入口处无解为止。 【建立解空间】 问题的解应该如何描述,如何建立呢?问题的解空间:应用回溯法解问题时,首先应明确定义问题的解空间。问题的解空间应到少包含问题的一个(最优)解。借助图论的思想,可以用图来描述,图的定义为G,由顶点集和边集构成,顶点即实实在在的数据、对象,而边可以抽象为关系,即顶点间的关系,这种关系不一定非要在数据结构上表现出来,用数据结构的语言来描述,如果关系是一对一,则为线性表,如果关系是一对多,则为树,如果关系是多对多,则为图,如果完全没有关系,则为集合。但在数据结构中这种关系不一定非要在数据的存储性质上一开始就表现出来,譬如,你可以用一个数组表示一个线性表,也可以表示完全二叉树,同样也可以用邻接表表示一个图,对于关系的描述不是数据结构本身的描述,而是算法的描述,正如数据结构是离不开特定的算法一样,不可分开单独而谈。 确定了解空间的组织结构后,回溯法就从开始结点(根结点)出发,以深度优先的方式搜索整个解空间。这个开始结点就成为一个活结点,同时也成为当前的扩展结点。在当前的扩展结点处,搜索向纵深方向移至一个新结点。这个新结点就成为一个新的活结点,并成为当前扩展结点。如果在当前的扩展结点处不能再向

使用分治策略递归和非递归和递推算法解决循环赛日程表课程设计报告

《算法设计与分析》 课程设计报告 题目:循环赛日程表 院(系):信息科学与工程学院 专业班级:软工 学生姓名: 学号: 指导教师: 2018 年 1 月 8 日至 2018 年 1 月 19 日

算法设计与分析课程设计任务书

目录 1 常用算法 (1) 1.1分治算法 (1) 基本概念: (1) 1.2递推算法 (2) 2 问题分析及算法设计 (5) 2.1分治策略递归算法的设计 (5) 2.2 分治策略非递归算法的设计 (7) 2.3 递推策略算法的设计 (8) 3 算法实现 (9) 3.1分治策略递归算法的实现 (9) 3.2 分治策略非递归算法的实现 (10) 3.3 递推策略算法的实现 (12) 4 测试和分析 (15) 4.1分治策略递归算法测试 (15) 4.2分治策略递归算法时间复杂度的分析 (16) 4.3 分治策略非递归算法测试 (16) 4.4分治策略非递归算法时间复杂度的分析 (17) 时间复杂度为:O(5^(n-1)) (17) 4.5 递推策略算法测试 (17) 4.6 递推策略算法时间复杂度的分析 (18) 时间复杂度为:O(5^(n-1)) (18) 4.7 三种算法的比较 (18) 5 总结 (19) 参考文献 (20)

1 常用算法 1.1分治算法 基本概念: 在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。这个技巧是很多高效算法的基础,如排序算法(快速排序,归并排序),傅立叶变换(快速傅立叶变换)…… 任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算。n=2时,只要作一次比较即可排好序。n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。 基本思想及策略: 分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。 分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。 如果原问题可分割成k个子问题,1

Problem_递推递归与贪心_

长郡中学NOIP递推递归贪心试题 试题名称程序名输入文件输出文件时限空间铺砖问题puzhuan. cpp puzhuan.in puzhuan.out 1s 128M 三角形计数TricountUVa. cpp TricountUVa.in TricountUVa.out 1s 128M 新汉诺塔Hanoi.cpp Hanoi.in Hanoi.out 1s 128M 生产调度 prod .cpp prod.in prod.out 1s 128M 最大乘积 maxmul.cpp maxmul.in maxmul.out 1s 128M 铺砖 【问题描述】 用1 x 1和2 x 2的磁砖不重叠地铺满N x 3的地板,共有多少种方案? 【输入】 仅一行包含一个正整数 N ( 1 <= N <= 1000 )。 【输出】 一行包含一个整数表示方案数,由于结果可能很大,你只需要输出这个答案mod 12345的值。 【样例】 puzhuan.in 2 puzhuan.out 3

三角形计数 【问题描述】 有多少种方法可以从1,2,3...,n中选3个不同的整数。使得以它们为三边长可以组成三角形? 比如n=5时有3种方法(2,3,4),(2,4,5),(3,4,5).n=8时有22种方法。 【输入】 输入包含多组测试数据,每组测试数据为一行整数n(3≤n≤1 000 000)。输入用n<3的标志结束。 【输出】 对于每组数据,输出其方案数(每组占一行) 【样例】 TricountUVa. IN 5 8 1 TricountUVa. OUT 3 22 数据组数不会超过20组。 对于25%的数据:(3≤n≤100) 对于50%的数据:(3≤n≤1 000) 对于100%的数据:(3≤n≤1 000 000)

递推-递归-分治-回溯

递推算法 在程序编辑过程中,我们可能会遇到这样一类问题,出题者告诉你数列的前几个数,或通过计算机获取了数列的前几个数,要求编程者求出第N项数或所有的数列元素(如果可以枚举的话),或求前N项元素之和。这种从已知数据入手,寻找规则,推导出后面的数的算法,称这递推算法。 典型的递推算法的例子有整数的阶乘,1,2,6,24,120…,a[n]=a[n-1]*n(a[1]=1);前面学过的2n,a[n]=a[n-1]*2(a[1]=1),菲波拉契数列:1,2,3,5,8,13…,a[n]=a[n-1]+a[n-2](a[1]=1,a[2]=2)等等。 在处理递推问题时,我们有时遇到的递推关系是十分明显的,简单地写出递推关系式,就可以逐项递推,即由第i项推出第i+1项,我们称其为显示递推关系。但有的递推关系,要经过仔细观察,甚至要借助一些技巧,才能看出它们之间的关系,我们称其为隐式的递推关系。 下面我们来分析一些例题,掌握一些简单的递推关系。 例如阶梯问题:题目的意思是:有N级阶梯,人可以一步走上一级,也可以一步走两级,求人从阶梯底走到顶端可以有多少种不同的走法。 这是一个隐式的递推关系,如果编程者不能找出这个递推关系,可能就无法做出这题来。我们来分析一下:走上第一级的方法只有一种,走上第二级的方法却有两种(两次走一级或一次走两级),走上第三级的走法,应该是走上第一级的方法和走上第二级的走法之和(因从第一级和第二级,都可以经一步走至第三级),推广到走上第i级,是走上第i-1级的走法与走上第i-2级的走法之和。很明显,这是一个菲波拉契数列。到这里,读者应能很熟练地写出这个程序。在以后的程序习题中,我们可能还会遇到菲波拉契数列变形以后的结果:如f(i)=f(i-1)+2f(i-2),或f(i)=f(i-1)+f(i-2)+f(i-3)等。 我们再来分析一下尼科梅彻斯定理。定理内容是:任何一个整数的立方都可以写成一串连续的奇数和,如:43=13+15+17+19=64。 从键盘输入一个整数n,要求写出其相应的连续奇数。 我们不妨从简单入手,枚举几个较小的数据: 13=1 23=3+5 33=7+9+11 43=13+15+17+19 53=21+23+25+27+29 根据上面的例子,读者不难看出: (1)输入为n时,输出应有n项。 (2)输入分别为1,2,3…时,则输出恰好为连续奇数,1,3,5,7,9,11…即下一行的首项比上一行的末项大2。 经上面的分析,原本看不出递推关系的问题,呈现出递推关系。在趣的是,这个例子的递推过程,可以有多种算法。 算法一:将所有奇数逐项例举出来,然后将其分段,即: 1; 3 5; 7 9 11; 13 15 17 19; 21… 1 2 3 4 5… 算法二、设输入为n时的输出第一项为a[n],则a[n]=a[n-1]-n+1; 于是我们推出首项后,则输出为a[n]+a[n]+2+…+a[n]+2(n-1) 算法三、进一步总结,不难得出,若输入为n时,首项a[n]=n2-n+1,其余同算法二。下面我们来分析两个与动物有关的趣题。

第3章-程序与递归:组合、抽象与构造

第3章程序与递归:组合、抽象与构造 1、关于计算系统与程序,下列说法正确的是_____。 (A)只有用计算机语言编写出来的代码才是程序,其他都不能称其为程序; (B)构造计算系统是不需要程序的,程序对构造计算系统没有什么帮助; (C)任何系统都需要程序,只是这个程序是由人来执行还是由机器自动执行,可以由机器自动执行程序的系统被称为计算系统; (D)程序是用户表达的随使用者目的不同而千变万化的复杂动作,不是使用者实现的而是需要计算系统事先完成的。 2、关于程序,下列说法不正确的是_____。 (A)“程序”是由人编写的、以告知计算系统实现人所期望的复杂动作; (B)“程序”可以由系统自动解释执行,也可以由人解释由系统执行; (C)普通人是很难理解“程序”的,其也和“程序”无关; (D)“程序”几乎和每个人都有关系,如自动售票系统、自动取款机等。 3、关于程序,下列说法不正确的是_____。 (A)程序的基本特征是复合、抽象与构造; (B)复合就是对简单元素的各种组合,即将一个(些)元素代入到另一个(些)元素中; (C)抽象是对各种元素的组合进行命名,并将该名字用于更复杂的组合构造中; (D)程序就是通过组合、抽象、再组合等构造出来的; (E)上述说法有不正确的。 4、一般而言,设计和实现一个计算系统,需要设计和实现_____。 (A)基本动作和程序; (B)基本动作和控制基本动作的指令; (C)基本动作、控制基本动作的指令和一个程序执行机构; (D)基本动作、控制基本动作的指令和程序。

5、一般而言,一个较高抽象层次的计算系统是可以这样实现的,即_____。 (A)将较低抽象层次的重复性组合,命名为较高抽象层次的指令; (B)利用较高抽象层次的指令进行复合、抽象与构造,即形成高抽象层次的程序; (C)高抽象层次的程序通过其程序执行机构解释为高抽象层次的指令及其操作次序; (D)高抽象层次的指令被替换为低抽象层次的程序,再由低抽象层次的程序执行机构解释并执行。 (E)上述A-D全部。 答案是:E 6、熟悉下列运算组合式(前缀表达式),其中结果为56的是_____。 (A) (* 7 (+ 5 2)); (B) (* (+ 5 3) (+ 5 2)); (C) (+ 20 (+ 6 6)); (D) (- (* 9 8) (- 20 2))。 //本题考查基本运算组合式的构造与计算,尤其是嵌套的运算组合式的计算 答案是:B 7、对于计算式,其正确的运算组合式(前缀表示法)为_____。 (A) (/ (+ 10 / 20 + 8 4) (+ * 3 6 * 8 2 )); (B) ((10 + (20 / (8 + 4))) / ((3 * 6) + (8 * 2))); (C) (/ (+ 10 (/ 20 (+ 8 4))) (+ (* 3 6) (* 8 2))); (D) (/ (/ 20 (+ 10 (+ 8 4))) (* (+ 3 6) (+ 8 2)))。 //本题考查运算组合式的书写与构造 答案是:C 8、请用define运算,定义一个过程实现计算a3,其正确定义的过程为_____。 (A) (define cube a (* a a a)); (B) (define (cube x) (* x x x)); (C) (define (cube a (* a a a))); (D) (define (cube a) (* x x x)))。

递归与递推

2.1 遍历问题 【问题描述】 我们都很熟悉二叉树的前序、中序、后序遍历,在数据结构中常提出这样的问题:已知一棵二叉树的前序和中序遍历,求它的后序遍历,相应的,已知一棵二叉树的后序遍历和中序遍历序列你也能求出它的前序遍历。然而给定一棵二叉树的前序和后序遍历,你却不能确定其中序遍历序列,考虑如下图中的几棵二叉树: 所有这些二叉树都有着相同的前序遍历和后序遍历,但中序遍历却不相同。 【输入】 输入数据共两行,第一行表示该二叉树的前序遍历结果s1,第二行表示该二叉树的后序遍历结果s2。 【输出】 输出可能的中序遍历序列的总数,结果不超过长整型数。 【样例】 travel.in abc cba travel.out 4 2.2 产生数 【问题描述】 给出一个整数n(n<1030)和m个变换规则(m≤20)。 约定:一个数字可以变换成另一个数字,规则的右部不能为零,即零不能由另一个数字变换而成。而这里所说的一个数字就是指一个一位数。 现在给出一个整数n和m个规则,要你求出对n的每一位数字经过任意次的变换(0次或多次),能产生出多少个不同的整数。 【输入】 共m+2行,第一行是一个不超过30位的整数n,第2行是一个正整数m,接下来的m 行是m个变换规则,每一规则是两个数字x、y,中间用一个空格间隔,表示X可以变换成Y。 【输出】 仅一行,表示可以产生的不同整数的个数。 【样例】 build.in 1 2 3

2 1 2 2 3 build.out 6 2.3 出栈序列统计 【问题描述】 栈是常用的一种数据结构,有n个元素在栈顶端一侧等待进栈,栈顶端另一侧是出栈序列。你已经知道栈的操作有两种:push和pop,前者是将一个元素进栈,后者是将栈顶元素弹出。现在要使用这两种操作,由一个操作序列可以得到一系列的输出序列。请你编程求出对于给定的n,计算并输出由操作数序列1,2,…,n,经过一系列操作可能得到的输出序列总数。 【输入】 就一个数n(1≤n≤1000)。 【输出】 一个数,即可能输出序列的总数目。 【样例】 stack.in 3 stack.out 5 2.4 计数器 【问题描述】 一本书的页数为N,页码从1开始编起,请你求出全部页码中,用了多少个0,1,2,…,9。其中一个页码不含多余的0,如N=1234时第5页不是0005,只是5。 【输入】 一个正整数N(N≤109),表示总的页码。 【输出】 共十行:第k行为数字k-1的个数。 【样例】 count.in 11 count.Out 1 4 1 1 1 1 1 1

递推与递归算法练习题2013.10

递推与递归算法练习题 1.实数数列(realsn) 源程序名realsn.??? (pas,c,cpp) 输入文件名realsn.in 输出文件名realsn.out 时间限制1秒 【问题描述】 一个实数数列共有n项,已知a[i]=(a[i-1]-a[i+1])/2+d,(1

用递推关系理论分析递归算法的时间复杂度

用递推关系理论分析递归算法的时间复杂度 [ 摘要 ] 对算法进行时间复杂度分析是算法分析与研究的重要内容,而对递归算法分析其时间复杂度时往往比较困难。 本文提出了用组合数学中的递推关系理论来分析一些特殊的递归算法的时间复杂度, 并同时得出三个推论, 在算法的分析与研究方面具有一定的参考价值。 [ 关键词 ] 时间复杂度,递归,母函数 1. 引言 一个程序在计算机上运行时所耗费的时间取决于对源程序进行编译所需时间、 计算机执行每条指令所需时间、 程序中指令重复执行的次数。 前两条依赖于实现算法的计算机软件、硬件系统,亦即依赖于实现算法所用语言的编译程序的性能和计算机本身的速度。因此习惯上常常用重复执行次数最多的语句频度来分析算法的时间复杂度, 记为t (n ),其中n 为问题的规模或大小。例如对n 个存放于数组a[n] 中的数进行选择法排序的算法: for( i=0; i %c", one, three); }else{ hanoi(n-1, one, three, two); printf("%c —> %c", one, three);

相关文档
最新文档