非数值型递归问题的求解方法

对于非数值问题编写递归程序的一般方法是:确定问题的最小模型并使用非递归算法解决,分解原来的非数值问题建立递归模型,确定递归模型的终止条件,将递归模型转换为递归程序。


  由于非数值型问题本身难于用数学公式表达。求解非数值问题的一般方法是要设计一种算法,找到解决问题的一系列操作步骤。如果能够找到解决问题的一系列递归的操作步骤,同样可以用递归的方法解决这些非数值问题。寻找非数值问题的递归算法可从分析问题本身的规律入手。可以按照下列步骤进行分析:
(1)从化简问题开始。将问题进行简化,将问题的规模缩到最小,分析问题在最简单情况下的求解方法。此时找到的求解方法应当十分简单。
(2)对于一个一般的问题,可将这个大问题分解为两个(或若干个)小问题,使原来的大问题变成这两个(或若干个)小问题的组合,其中至少有一个小问题与原来的问题有相同的性质,只是在问题的规模上与原来的问题相比较有所缩小。
(3)将分解后的每个小问题作为一个整体,描述用这些较小的问题来解决原来大问题的算法。 
由第3步得到的算法就是一个解决原来问题的递归算法。由第1步将问题的规模缩到最小时的条件就是该递归算法的递归结束条件。
例9-30:输入一个正整数,要求以相反的顺序输出该数。用递归方法实现。
第1步,先将问题进行简化。假设要输出的正整数只有一位,则该问题就简化为"反向"输出一位正整数。对一位整数实际上无所谓"正"与"反",问题简化为输出一位整数。这样简化后的问题可以很容易实现。
第2步,对于一个大于10的正整数,在逻辑上可以将它分为两部分:个位上数字和个位以前的全部数字。 
第3步,将个位以前的全部数字看成一个整体,则为了反向输出这个大于10的正整数,可以按如下步骤进行操作:
①输出各位上的数字
②反向输出个位以前的全部数字
这就是将原来的问题分解后,用较小的问题来解决原来大问题的算法。其中操作②中的问题"反向输出个位以前的全部数字"只是对原问题在规模上进行了缩小。这样描述的操作步骤就是一个递归的操作步骤。


  整理上述分析结果,把第1步中化简问题的条件作为递归结束条件,将第3步分析得到的算法作为递归算法,可以写出如下完整的递归算法描述。
  若  要输出的整数位一位
     则  输出该整数
     否则 输出整数的个位数字,反向输出除个位以外的全部数字;
  结束
按照这样的算法可以编出如下程序:
main( )
{ int num;
printf("Enter number:");
scanf ("%d", &num);
printn (num);
printf ("\n");
}
printn (n) /* 反向输出整数n */
int n;
{ if ( 0<=n && n<=9 ) /* 若n为一位整数 */
printf("%d", n); /* 则 输出整数n */
else { /* 否则 */
printf ("%d", n%10); /* 输出n的个位数字  */
printn (n/10); /* 递归操作反向输出除 */
} /* 个位以外的全部数字 */
}


  例9-31:汉诺塔(Hanoi)问题是一个著名的问题。约十九世纪末,在欧洲的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔,游戏的目的是将最左边A杆上的圆盘,借助最右边的C杆,全部移到中间的B杆上,条件是一次仅能移动一个盘,且不允许大盘放在小盘的上面。 
相传古代印度布拉玛神庙中有一个僧人,他每天不分白天黑夜,不停地移动那些圆盘,据说,当所有64个圆盘全部从一根杆上移到另一根杆上的那一天就是世界的末日。故汉诺塔问题又被称为"世界末日问题"。

由于问题中给出的圆盘移动条件是:一次仅能移动一个盘,且不允许大盘放在小盘的上面,这样64个盘子的移动次数是:18,446,744,073,709,551,616。
这是一个天文数字,若每一微秒可能计算(并不输出)一次移动,那么也需要几乎一百万年。我们仅能找出问题的解决方法并解决较小N值时的汉诺塔,但目前由于计算机的速度还不够"快",尚不可能用计算机解决64层的汉诺塔。
按照上面给出的方法分析问题,找出移动圆盘的递归算法。
设要解决的汉诺塔共有N个圆盘,对A杆上的全部N个圆盘从小到大顺序编号,最小的圆盘为1号,次之为2号,依次类推,则最下面最大的圆盘的编号为N。
第1步,先将问题简化。假设A杆上只有一个圆盘,即汉诺塔只有一层N=1,则只要将1号盘从A杆上移到B杆上即可。
第2步,对于一个有N(N>1)个圆盘的汉诺塔,将N个圆盘分为两部分:上面的N-1个圆盘和最下面的N号圆盘。
第3步,将"上面的N-1个圆盘"看成一个整体,为了解决N个圆盘的汉诺塔,可以按如下方式进行操作: 
① A杆上面的N-1个盘子,借助B杆,移到C杆上;

② A杆上剩下的N号盘子移到B杆上;

③ C杆上的N-1个盘子,借助A杆,移到B杆上。

图9-9 汉诺塔问题求解算法图示
整理上述分析结果,把第1步中化简问题的条件作为递归结束条件,将第3步分析得到的算法作为递归算法,可以写出如下完整的递归算法描述。
定义一个函数movedisc(n,fromneedle,toneedle,usingneedle)。该函数的功能是:将fromneedle杆上的N个圆盘,借助usingneedle杆,移动到toneedle杆上。这样移动N个圆盘的递归算法描述如下:
movedisc(n,fromneedle,toneedle,usingneedle)
{ if ( n==1 ) 将n号圆盘从fromneedle上移到toneedle;
else {
① movedisc(n-1,fromneedle,usingneedle,toneedle)
② 将n号圆盘从fromneedle上移到toneedle;
③ movedisc(n-1,usingneedle,toneedle,fromneedle)
}
}
按照上述算法可以编出如下程序。
int i=0; /* 移动圆盘数量计数器 */
main( )
{ unsigned n;
printf("Please enter the number of discs:");
scanf ("%d", &n); /* 输入N值 */
movedisc (n, 'a', 'b', 'c'); /* 将A上的N个圆盘借助C将移动到B上 */
printf("\t Total: %d\n", i);
}
movedisc( n, fromneedle, toneedle, usingneedle)
/* movedisc函数完成的功能是:将fromneedle杆上的n个圆盘借助 */
/* usingneedle杆移动到toneedle杆上 */
unsigned n;
char fromneedle, toneedle, usingneedle;
{ if ( n==1 )
printf("%2d-(%2d): %c ==> %c\n", ++i, n, fromneedle, toneedle);
/* 将fromneedle上的一个圆盘移到toneedle上 */
else {
movedisc(n-1, fromneedle, usingneedle, toneedle);
/* 将fromneedle上的N-1个圆盘借助toneedle移到usingneedle上 */
printf("%2d-(%2d): %c ==> %c\n", ++i, n, fromneedle, toneedle);
/* 将fromneedle上的一个圆盘移到toneedle上 */
movedisc (n-1, usingneedle, toneedle, fromneedle);
/* 将usingneedle上的N-1个圆盘借助fromneedle移到toneedle上 */
}
}
输入N=3,程序的运行结果为:
Please enter the number of discs: 3
1-( 1): a ==> b
2-( 2): a ==> c
3-( 1): b ==> c
4-( 3): a ==> b
5-( 1): c ==> a
6-( 2): c ==> b
7-( 1): a ==> b
Total: 7
当N=3时,程序递归调用的完整执行过程(递归调用的层次、实参值的变化、递归调用的关系、返回关系、输出结果)可用图9-10表示。

在main函数中 递归调用第一层 递归调用第二层 递归调用第三层
递归 递归
┎──────→ movedisc(1,a,b,c) ─→ 输出:1-(1):a==>b
┃ ←────────── 返回递归第二层
┃ 输出:2-(2):a==>c
调用 ┃ movedisc(1,b,c,a) ─→ 输出:3-(1):b==>c
┎─────→ movedisc(2,a,c,b) ←────────── 返回递归第二层
┃ ←───────── 返回递归调用第一层
movedisc(3,a,b,c) 输出:4-(3):a==>b
←───┐ 
│ movedisc(2,c,b,a) → movedisc(1,c,a,b) ─→ 输出:5-(1):c==>a
│ ←───────┐ ←────────── 返回递归第二层
│ │ 输出:6-(2):c==>b
└──── 返回main函数 │ movedisc(1,a,b,c) ─→ 输出:7-(1):a==>b
│ ←────────── 返回递归第二层
└─ 返回递归调用第一层
图9-10 N=3时汉诺塔程序的执行过程

<!--EndFragment-->
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值