函
数
的
递归调
用
一 个 函 数 在 它 的函 数 体 内 调 用 它 自身 称 为递归调 用。 这种 函 数称 为递归 函 数 。C 语 言允 许 函 数 的 递归调 用。在 递归调 用中, 主 调 函 数 又是被 调 函 数 。 执 行 递归 函 数将 反 复调 用其自身。 每 调 用一次就 进 入新的一 层 。例如有函 数 f 如下:
int f (int x)
{
int y;
z=f(y);
return z;
}
这 个 函 数 是一 个 递归 函 数 。 但是 运 行 该 函 数将 无休止地 调 用其自身, 这 当 然是不正确的。 为 了防止 递归调 用无 终 止地 进 行, 必 须 在函 数内 有 终 止 递归调 用的手段。常用的 办 法是加 条 件判 断 , 满 足某 种 条 件后就不再作 递归调 用,然后逐 层 返回。 下面 举 例 说 明 递归调 用的 执 行 过 程。
[例5.9]用 递归 法 计 算 n!用 递归 法 计 算 n!可用下述公式表示:
n!=1 (n=0,1)
n×(n-1)! (n>1)
按公式可 编 程如下:
long ff(int n)
{
long f;
if(n<0) printf("n<0,input error");
else if(n==0||n==1) f=1;
else f=ff(n-1)*n;
return(f);
}
程序中 给 出的函 数 ff是一 个 递归 函 数 。 主函 数 调 用 ff 后即 进 入函 数 ff 执 行,如果 n<0,n==0 或 n=1 时 都 将 结 束函 数 的 执 行,否 则 就 递归调 用 ff 函 数 自身。由于 每 次 递归调 用的 实 参 为 n-1 ,即把 n-1 的 值赋 予形 参 n ,最后 当 n-1 的 值为 1 时 再作 递归调 用,形 参 n 的 值 也 为 1 , 将 使 递归终 止。然后可逐 层 退回。下面我 们 再 举 例 说 明 该过 程。 设执 行本程序 时输 入 为 5 , 即求 5! 。在主函 数 中的 调 用 语 句即 为 y=ff(5) , 进 入 ff 函 数 后,由于 n=5, 不等于 0 或 1 ,故 应执 行 f=ff(n-1)*n, 即 f=ff(5-1)*5 。 该语 句 对 ff 作 递归调 用即 ff(4) 。 逐次 递归 展 开 如 图 5.3 所示。 进 行四次 递归调 用后, ff 函 数 形 参 取得的 值变为 1 ,故不再 继续递归调 用而 开 始逐 层 返回主 调 函 数 。 ff(1) 的函 数 返回 值为 1 , ff(2) 的返回 值为 1*2=2 , ff(3) 的返回 值为 2*3=6 , ff(4) 的返
回 值为 6*4=24 ,最后返回 值 ff(5) 为 24*5=120 。
例 5. 9 也可以不用 递归 的方法 来 完成。如可以用 递 推法,即 从 1 开 始乘以 2 ,再乘以 3… 直到 n 。 递 推法比 递归 法更容易理解和 实现 。但是有些 问题则 只能用 递归 算法才能 实现 。典型的 问题 是 Hanoi 塔 问题 。
[ 例 5.10]Hanoi 塔 问题
一 块 板上有三根 针 , A , B , C 。 A 针 上套有 64 个 大小不等的 圆盘 , 大的在下,小的在上。如 图 5.4 所示。要把 这 64 个 圆盘 从 A 针 移 动 C 针 上, 每 次只能移 动 一 个 圆盘 ,移 动 可以借助 B 针进 行。但在任何 时 候,任何 针 上的 圆盘 都必 须 保持大 盘 在下,小 盘 在上。求移 动 的 步 骤 。
本 题 算法分析如下, 设 A 上有 n 个 盘 子。
如果n=1, 则 将 圆盘 从 A直接移 动 到 C。
一 个 函 数 在 它 的函 数 体 内 调 用 它 自身 称 为递归调 用。 这种 函 数称 为递归 函 数 。C 语 言允 许 函 数 的 递归调 用。在 递归调 用中, 主 调 函 数 又是被 调 函 数 。 执 行 递归 函 数将 反 复调 用其自身。 每 调 用一次就 进 入新的一 层 。例如有函 数 f 如下:
int f (int x)
{
int y;
z=f(y);
return z;
}
这 个 函 数 是一 个 递归 函 数 。 但是 运 行 该 函 数将 无休止地 调 用其自身, 这 当 然是不正确的。 为 了防止 递归调 用无 终 止地 进 行, 必 须 在函 数内 有 终 止 递归调 用的手段。常用的 办 法是加 条 件判 断 , 满 足某 种 条 件后就不再作 递归调 用,然后逐 层 返回。 下面 举 例 说 明 递归调 用的 执 行 过 程。
[例5.9]用 递归 法 计 算 n!用 递归 法 计 算 n!可用下述公式表示:
n!=1 (n=0,1)
n×(n-1)! (n>1)
按公式可 编 程如下:
long ff(int n)
{
long f;
if(n<0) printf("n<0,input error");
else if(n==0||n==1) f=1;
else f=ff(n-1)*n;
return(f);
}
程序中 给 出的函 数 ff是一 个 递归 函 数 。 主函 数 调 用 ff 后即 进 入函 数 ff 执 行,如果 n<0,n==0 或 n=1 时 都 将 结 束函 数 的 执 行,否 则 就 递归调 用 ff 函 数 自身。由于 每 次 递归调 用的 实 参 为 n-1 ,即把 n-1 的 值赋 予形 参 n ,最后 当 n-1 的 值为 1 时 再作 递归调 用,形 参 n 的 值 也 为 1 , 将 使 递归终 止。然后可逐 层 退回。下面我 们 再 举 例 说 明 该过 程。 设执 行本程序 时输 入 为 5 , 即求 5! 。在主函 数 中的 调 用 语 句即 为 y=ff(5) , 进 入 ff 函 数 后,由于 n=5, 不等于 0 或 1 ,故 应执 行 f=ff(n-1)*n, 即 f=ff(5-1)*5 。 该语 句 对 ff 作 递归调 用即 ff(4) 。 逐次 递归 展 开 如 图 5.3 所示。 进 行四次 递归调 用后, ff 函 数 形 参 取得的 值变为 1 ,故不再 继续递归调 用而 开 始逐 层 返回主 调 函 数 。 ff(1) 的函 数 返回 值为 1 , ff(2) 的返回 值为 1*2=2 , ff(3) 的返回 值为 2*3=6 , ff(4) 的返
回 值为 6*4=24 ,最后返回 值 ff(5) 为 24*5=120 。
例 5. 9 也可以不用 递归 的方法 来 完成。如可以用 递 推法,即 从 1 开 始乘以 2 ,再乘以 3… 直到 n 。 递 推法比 递归 法更容易理解和 实现 。但是有些 问题则 只能用 递归 算法才能 实现 。典型的 问题 是 Hanoi 塔 问题 。
[ 例 5.10]Hanoi 塔 问题
一 块 板上有三根 针 , A , B , C 。 A 针 上套有 64 个 大小不等的 圆盘 , 大的在下,小的在上。如 图 5.4 所示。要把 这 64 个 圆盘 从 A 针 移 动 C 针 上, 每 次只能移 动 一 个 圆盘 ,移 动 可以借助 B 针进 行。但在任何 时 候,任何 针 上的 圆盘 都必 须 保持大 盘 在下,小 盘 在上。求移 动 的 步 骤 。
本 题 算法分析如下, 设 A 上有 n 个 盘 子。
如果n=1, 则 将 圆盘 从 A直接移 动 到 C。
.3
如果 n=2 , 则 :
1. 将 A 上的 n-1( 等于 1) 个 圆盘 移到 B 上;
2. 再 将 A 上的一 个 圆盘 移到 C 上;
3. 最后 将 B 上的 n-1( 等于 1) 个 圆盘 移到 C 上。
如果 n=3 , 则 :
A. 将 A 上的 n-1( 等于 2 ,令其 为 n`) 个 圆盘 移到 B( 借助于 C) ,
步 骤 如下:
(1) 将 A 上的 n`-1( 等于 1) 个 圆盘 移到 C 上, 见图 5.5(b) 。
(2) 将 A 上的一 个 圆盘 移到 B , 见图 5.5(c)
(3) 将 C 上的 n`-1( 等于 1) 个 圆盘 移到 B , 见图 5.5(d)
B. 将 A 上的一 个 圆盘 移到 C , 见图 5.5(e)
C. 将 B 上的 n-1( 等于 2 ,令其 为 n`) 个 圆盘 移到 C( 借助 A) ,
步 骤 如下:
(1) 将 B 上的 n`-1( 等于 1) 个 圆盘 移到 A , 见图 5.5(f)
(2) 将 B 上的一 个 盘 子移到 C , 见图 5.5(g)
(3) 将 A 上的 n`-1( 等于 1) 个 圆盘 移到 C , 见图 5.5(h) 。
到此,完成了三 个 圆盘 的移 动过 程。
从 上面分析可以看出, 当 n 大于等于 2 时 , 移 动 的 过 程可分解 为
三 个 步 骤 :
第一 步 把 A 上的 n-1 个 圆盘 移到 B 上;
第二 步 把 A 上的一 个 圆盘 移到 C 上;
第三 步 把 B 上的 n-1 个 圆盘 移到 C 上;其中第一 步 和第三 步 是 类 同的。
当 n=3 时 ,第一 步 和第三 步 又分解 为类 同的三 步 ,即把 n`-1 个 圆盘 从 一 个 针 移到另一 个 针 上, 这 里的 n`=n-1 。 显 然 这 是一 个 递归过
程,据此算法可 编 程如下:
move(int n,int x,int y,int z)
{
if(n==1)
printf("%c-->%c/n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c/n",x,z);
move(n-1,y,x,z);
}
}
main()
{
int h;
printf("/ninput number:/n");
scanf("%d",&h);
printf("the step to moving %2d diskes:/n",h);
move(h,'a','b','c');
}
move(int n,int x,int y,int z)
{
if(n==1)
printf("%-->%c/n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c/n",x,z);
move(n-1,y,x,z);
}
}
main()
{ ……
move(h,'a','b','c');
}
从 程序中可以看出,move函 数 是一 个 递归 函 数 , 它 有四 个 形 参 n,x,y,z。 n 表示 圆盘 数 , x,y,z 分 别 表示三根 针 。 move 函 数 的功能是把 x 上的 n 个 圆盘 移 动 到 z 上。 当 n==1 时 ,直接把 x 上的 圆盘 移至 z 上, 输 出 x→z 。如 n!=1 则 分 为 三 步 : 递归调 用 move 函 数 ,把 n-1 个 圆盘 从 x 移到 y ; 输 出 x→z ; 递归调 用 move 函 数 ,把 n-1 个 圆盘 从 y 移到 z 。在 递归调 用 过 程中 n=n-1 ,故 n 的 值 逐次 递 减 ,最后 n=1 时 , 终 止 递归 ,逐 层 返回。 当 n=4 时 程序 运 行的 结 果 为
input number:
4
the step to moving 4 diskes:
a→b
a→c
b→c
a→b
c→a
c→b
a→b
a→c
b→c
b→a
c→a
b→c
a→b
a→c
b→c
如果 n=2 , 则 :
1. 将 A 上的 n-1( 等于 1) 个 圆盘 移到 B 上;
2. 再 将 A 上的一 个 圆盘 移到 C 上;
3. 最后 将 B 上的 n-1( 等于 1) 个 圆盘 移到 C 上。
如果 n=3 , 则 :
A. 将 A 上的 n-1( 等于 2 ,令其 为 n`) 个 圆盘 移到 B( 借助于 C) ,
步 骤 如下:
(1) 将 A 上的 n`-1( 等于 1) 个 圆盘 移到 C 上, 见图 5.5(b) 。
(2) 将 A 上的一 个 圆盘 移到 B , 见图 5.5(c)
(3) 将 C 上的 n`-1( 等于 1) 个 圆盘 移到 B , 见图 5.5(d)
B. 将 A 上的一 个 圆盘 移到 C , 见图 5.5(e)
C. 将 B 上的 n-1( 等于 2 ,令其 为 n`) 个 圆盘 移到 C( 借助 A) ,
步 骤 如下:
(1) 将 B 上的 n`-1( 等于 1) 个 圆盘 移到 A , 见图 5.5(f)
(2) 将 B 上的一 个 盘 子移到 C , 见图 5.5(g)
(3) 将 A 上的 n`-1( 等于 1) 个 圆盘 移到 C , 见图 5.5(h) 。
到此,完成了三 个 圆盘 的移 动过 程。
从 上面分析可以看出, 当 n 大于等于 2 时 , 移 动 的 过 程可分解 为
三 个 步 骤 :
第一 步 把 A 上的 n-1 个 圆盘 移到 B 上;
第二 步 把 A 上的一 个 圆盘 移到 C 上;
第三 步 把 B 上的 n-1 个 圆盘 移到 C 上;其中第一 步 和第三 步 是 类 同的。
当 n=3 时 ,第一 步 和第三 步 又分解 为类 同的三 步 ,即把 n`-1 个 圆盘 从 一 个 针 移到另一 个 针 上, 这 里的 n`=n-1 。 显 然 这 是一 个 递归过
程,据此算法可 编 程如下:
move(int n,int x,int y,int z)
{
if(n==1)
printf("%c-->%c/n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c/n",x,z);
move(n-1,y,x,z);
}
}
main()
{
int h;
printf("/ninput number:/n");
scanf("%d",&h);
printf("the step to moving %2d diskes:/n",h);
move(h,'a','b','c');
}
move(int n,int x,int y,int z)
{
if(n==1)
printf("%-->%c/n",x,z);
else
{
move(n-1,x,z,y);
printf("%c-->%c/n",x,z);
move(n-1,y,x,z);
}
}
main()
{ ……
move(h,'a','b','c');
}
从 程序中可以看出,move函 数 是一 个 递归 函 数 , 它 有四 个 形 参 n,x,y,z。 n 表示 圆盘 数 , x,y,z 分 别 表示三根 针 。 move 函 数 的功能是把 x 上的 n 个 圆盘 移 动 到 z 上。 当 n==1 时 ,直接把 x 上的 圆盘 移至 z 上, 输 出 x→z 。如 n!=1 则 分 为 三 步 : 递归调 用 move 函 数 ,把 n-1 个 圆盘 从 x 移到 y ; 输 出 x→z ; 递归调 用 move 函 数 ,把 n-1 个 圆盘 从 y 移到 z 。在 递归调 用 过 程中 n=n-1 ,故 n 的 值 逐次 递 减 ,最后 n=1 时 , 终 止 递归 ,逐 层 返回。 当 n=4 时 程序 运 行的 结 果 为
input number:
4
the step to moving 4 diskes:
a→b
a→c
b→c
a→b
c→a
c→b
a→b
a→c
b→c
b→a
c→a
b→c
a→b
a→c
b→c
在
实际问题
中,一
组
数
据往往具有不同的
数
据
类
型
。例如, 在
学
生登
记
表中,姓名
应为
字符型;
学号
可
为
整型或字符型;
年
龄应为
整型;性
别应为
字符型;成
绩
可
为
整型或
实
型。
显
然不能用一
个数
组
来
存放
这
一
组
数
据。
因
为
数
组
中各元素的
类
型和
长
度都
必
须
一致,以便于
编译
系
统处
理。
为
了解
决
这
个
问题
,C
语
言中
给
出了另一
种
构
造
数
据
类
型
——“
结
构
”
。
它
相
当
于其
它
高
级语
言中的
记录
。