Pascal递归

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

四、应用举例—斐波那契数列



有一种兔子,出生后一个月就可以长大, 然后再过一个月一对长大的兔子就可以生 育一对小兔子且以后每个月都能生育一对。 现在,我们有一对刚出生的这种兔子,那 么,n个月过后,我们会有多少对兔子呢? 假设所有的兔子都不会死亡。 输入:5 输出:5
兔子出生演示
1 1
1月
2月
procedure search(k: integer); begin if k > n then 得到一组全排列 else for i := 1 to n do if (hash[i] = 0) then begin hash[i] := 1; result[k] := i; search(k+1); hash[i] := 0; end end;


n皇后问题
function check: boolean; var i, j: integer; begin check := true; for i := 2 to n do for j := 1 to i - 1 do if (i - j) = abs(result[i] - result[j]) then begin check := false; exit; end; end;
n皇后问题


但是考虑到皇后攻击的特性,所有的皇后 不能同行、同列,因此,我们可以人为的 限定所有的皇后不同行、同列,这样的话, 所有的可能性就只有n!种了。 我们只要枚举出所有这n!种排布,找出其中 满足任意两皇后都不共对角线的所有情况 即可。
n皇后问题

那么如何来枚举这n!种排布呢?
既然所有的皇后都不能同行或同列,那么不妨 我们先人为规定第k个皇后在第k行,这样的话 我们只要给出每个皇后所处的列号就可以描述 皇后的位置了。如图放置方式就可以被表示为: 2、4、1、3 即第1个皇后在第2列,第2个 皇后在第4列,…… 因此求这n!种排布就被转为 求1~n的全排列
斐波那契数列


因此,我们可以得到递归公式: F(n)=F(n-1)+F(n-2) 而递归的终止条件则是: n=1或n=2时,F(n)=1
斐波那契数列
function F(k: integer): longint; begin if (k = 1) or (k = 2) then F := 1 else F := F(k - 1) + F(k - 2) end; begin read(n); writeln(F(n)); end.
2.
3.

可以看出,这是一个递归的定义,递归的结束 条件为k>n。
n皇后问题
procedure search(k: integer); begin if k > n then 得到一组全排列 else for i := 1 to n do if (i未出现过) then begin 标记i已出现; 记录第k个数是i; search(k+1); 标记i未出现; end; end;

在计算机中,是通过使用系统栈来完成上 述操作的。系统栈的元素会包括值参、局 部变量和返回地址。在每次执行递归调用 之前,系统自动把本程序所使用到的值参 和局部变量的当前值以及调用后的返回地 址压栈(保存现场);当每次递归调用结 束之后,系统又自动把栈顶元素出栈,覆 盖掉相应的值参和局部变量,使他们恢复 到递归调用之前的值(恢复现场),然后 程序无条件的转向由返回地址所指定的位 置继续执行。
2
2 3
1
1
3月 4月
5
2
3
4
1
5月
斐波那契数列



从上面的分析可以看到,第n个月后兔子的数目有 两部分构成:1、上月的所有兔子;2、上月的所 有老兔子所生的小兔子。 因此,若我们记第n个月的兔子总数为F(n)、老兔 子数为G(n)的话,那么F(n)=F(n-1)+G(n-1)。 那么G(n)怎么得到呢?通过分析上图,我们可以 发现,第n个月的老兔子数等于第n-1个月的兔子 总数,因此上式中的G(n-1)=F(n-2)。
为被调用过程的局部变量分配存储区; 将所有的实在参数、返回地址等信息传递给被调用过 程保存; 将控制转移到被调过程的入口。
3.

从被调用过程返回调用过程之前,系统也应完成 三件工作:
1. 2. 3.
保存被调过程的计算结果; 释放被调过程的数据区; 依照被调过程保存的返回地址将控制转移到调用过程。
三、递归算法的执行过程
对于一个递归定义而已,除了要定义递归 的方式(即如何递归)外,还必须定义递 归的终止条件(即如何停止递归),否则 递归将永无止境的进行下去。
n0 1 f ( n) n * f (n 1) n 0
n 0、 1 n f ( n) f ( n 1) f ( n 2) n 1
Pascal语言中的向前引用
procedure b; forward; procedure a; begin b; end;
procedure b;
begin a; end;
二、递归算法用于解决的问题

一般来说,能够用递归解决的问题应该满 足以下三个条件:
1.
2.
需要解决的问题可以化为一个或多个子问题 来求解,而这些子问题的求解方法与原来的 问题完全相同,只是在数量规模上不同; 递归调用的次数必须是有限的;
四、应用举例—n皇后问题
在一个n×n的棋盘上放置n个国际象棋中 的皇后,要求所有的皇后之间都不形成 攻击。请你给出所有可能的排布方案数。 输入:4 输出:2

n 总数
4 2
5 10
6 4
7 40
8 92
n皇后问题


对于n皇后问题而言,我们很难找出很合适 的方法来快速的得到解,因此,我们只能 采取最基本的枚举法来求解。 但我们知道,在n×n的棋盘上放置n个棋子 的所有放置方案有 n 2 种,而这个数字是否 Cn 庞大,直接枚举肯定会超时。
n皇后问题
procedure search(k: integer); begin if k > n then begin if check() then inc(count); end else for i := 1 to n do if (hash[i] = 0) then begin hash[i] := 1; result[k] := i; search(k+1); hash[i] := 0; end end;
三、递归算法的执行过程
n0 1 n! n (n 1)! n 0
主程序 =120 Fac(5) =24 5*Fac(4) =6 =2 =1 =1
4*Fac(3)
3*Fac(2)
2*Fac(1)
1*Fac(0) 1
三、递归算法的执行过程

在递归调用之前,系统需完成三件事:
1. 2.
n皇后问题


如何记录某个数字是否已经被使用过呢? 很容易想到可以用集合或者一维数组来实 现。 比如我们用一维数组hash[1..n]来记录的话 hash[i]=1代表i已经被使用过 hash[i]=0代表i没有被使用过 同时我们使用一个一维数组result[1..n]来记 录生成的全排列
n皇后问题
主程序 =120 Fac(5)
n*Fac(3)
n*Fac(2)
n=3,retaddr=6
n=4,retaddr=6 n=5,retaddr=6 1
n*Fac(1)
n*Fac(0)
四、应用举例—汉诺塔问题

如图所示,我们有三根柱子(A、B、C)和若干 大小各异的圆盘,一开始的时候,所有圆盘都在 A柱上,且按照从小到大的顺序排列整齐(小的 在上,大的在下),现在请你把所有的圆盘从A 柱搬到C柱上去。在搬动的时候,一次只能将某柱 最顶端的一个圆盘移动到另一柱的最顶端,且在 移动的过程中,永远不允许出现大圆盘在小圆盘 之上的情况。
n皇后问题

当得到一个全排列后,我们只要检查一下 是否满足所有的皇后都不共对角线即可。
我们知道,判断是否共对角线只要判断一 下两个位置行、列差的绝对值是否相等即 可。 abs(i-j) = abs(result[i] - result[j]) 由于具有对称性,因此为了提高检查的效 率,我们假设i>j。

n皇后问题

求1~n的全排列,我们可以采用这样的思路, 假设当前生成全排列中的第k个数字,如果k>n, 则说明生成了一个全排列,否则:
1.
从1~n依次尝试,如果此数字前面已经出现,则取下 一个数字,直到找到第一个没有出现的数字; 把这个数字标记未已出现,然后以同样的方法生成全 排列中的下一个数字; 把这个数字标记未未出现。
3.
必须有结束递归的条件(边界条件)来终止 递归。
二、递归算法用于解决的问题

可用递归解决的具体问题:
1. 2. 3.
数据的定义形式是按递归定义的。如阶乘。 问题的解法按递归算法实现。如回溯算法。 function jc(n: integer): longint; begin 数据结构的形式是按递归定义的。如树的遍历。
if (n = 0) then procedure m-search(t: node) jc := begin 1 else <> nil) then if (t jc := beginn * jc(n-1); end; m-search(t.left); visit(t); m-search(l.right); end; end;
程序一般调用
主程序 子程序 A 主程序 子程序 A 子程序 B 子程序 B …… 调用 A …… 主程序
子程序 A
调用 A
调用 B
程序递归调用
主程序 子程序 A …… 调用 A …… 主程序 子程序 A 主程序 子程序 A …… 调用 B …… 子程序 B …… 调用 A ……
子程序 B …… 调用 A ……
begin read(n); fillchar(hash, sizeof(hash), 0); count := 0; search(1); writeln(count); end.
汉诺塔问题

输入:3 输出: A->C A->B C->B A->C B->A B->C A->C
演示分析
汉诺塔问题

只有一个盘子的移动:AC 两个盘子的移动:AB,AC,BC n个盘子的移动(n>2):
将上面的n-1个盘子看成一个整体,则此问题 被转换为两个盘子的移动 而n-1个盘子的移动规则与此相同
三、递归算法的执行过程
1:function jc(n: integer): longint; 2:begin 3: if (n = 0) then 4: jc := 1 5: else 6: jc := n * jc(n-1); n=0,retaddr=6 7:end;
=24 n*Fac(4) =6 =2 =1 =1 n=1,retaddr=6 n=2,retaddr=6
第七讲
递归
一、递归的概念

若在一个函数、过程或者数据结构定义的 内部,直接(或间接)出现定义本身的应 用,则称它们是递归的,或者是递归定义 的。
递归是一种强有力的数学工具,它可使问 题的描述和求解变得简洁和清晰。
n!=n*(n-1)! F(n)=F(n-1)+F(n-2)


一、递归的概念


汉诺塔问题的递归描述

当只移动一个盘子的时候,直接从起始柱 移动到目标柱,否则,执行如下操作:
1.
2. 3.
将前n-1盘子以相同的方法从起始柱移动到临 时柱; 将第n个盘子直接移动到目标柱; 将前n-1个盘子以相同的方法从临时柱移动到 目标柱anoi(n: integer; a, b, c: char); // n要移动的盘子数目,a:起始柱,c:目标柱,b:临时柱 begin if (n = 1) then writeln(a, ‘->’, c) // 直接将起始柱顶端的盘子移动到目标柱 else begin hanoi(n-1, a, c, b); // 递归移动前n-1个盘子从起始柱移动到临时柱 writeln(a, ‘->’, c); // 直接将最后一个盘子从起始柱移动到目标柱 hanoi(n-1, b, a, c); // 递归移动前n-1个盘子从临时柱移动到目标柱 end; end; begin read(n); hanoi(n, ‘A’, ‘B’, ‘C’); // 将n个盘子从A柱移动到C柱 end.
相关文档
最新文档