函数是完成某种功能的程序段,是程序模块化的体现。对于一个复杂的问题,可以将其分解成若干个子问题来解决,如果子问题依然很复杂,还可以将它继续分解。直到每个子问题都是一个具有独立任务的模块。以这种方式编写的程序结构清晰,逻辑关系明确,会给编写、阅读、调试以及修改带来很多好处。
一个程序可以有许多函数,包括主函数和非主函数,主函数只能有一个,非主函数可以有多个。主函数自动执行,非主函数只有被调用时才会执行。程序从主函数开始执行到主函数结束而结束,主函数可以调用任何非主函数,非主函数之间可以互相调用,但不能调用主函数。函数调用过程中,调用其他函数的函数叫作主调函数,相应的被调用的函数叫作被调函数。
提示
主函数和主调函数是不同的。主函数特指 main ()函数,主调函数是指调用其他函数的函数。主调函数可以是主函数,也可以是非主函数。
函数的递归调用是指函数自身调用自身,这是一类特殊的函数嵌套。
递归函数的一般格式
数据类型 函数名(形参列表) {
if<递归结束条件> return 返回值;
else return 函数名(形参该变量)
}
例如
long long factor(int n) {
if(n==0) return 1;
else return n*factor(n-1);
}
构成递归需要具备两个条件:
非递归的一般实现代码。
long long factorial(int n) {
long long s=1;
for(int i=1; i<=n; i++) {
s *= i;
}
return s;
}
递归方式的代码。
long long factorial(int n) {
if(n==0) {
return 1;
}
else {
return n*factorial(n-1);
}
}
下面是factorial(2),即当实参为2时的递归调用过程:
Hanoi (汉诺)塔问题。这是一个古典的数学问题,是一个用递归方法解题的典型例子。问题是这样的:古代有一个梵塔,塔内有3个座 A 、 B 、 C ,开始时 A 座上有64个盘子,盘子大小不等,大的在下,小的在上(图2)。有一个老和尚想把这64个盘子从 A 座移到 C 座,但每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用 B 座,要求编程序输出移动的步骤。
图2 Hanoi(汗诺)塔问题
为便于理解,先分析将 A 座上3个盘子移到 C 座上的过程:
1 将 A 座上2个盘子移到 B 座上(借助 C );
2 将 A 座上1个盘子移到 C 座上;
3 将 B 座上2个盘子移到 C 座上(借助 A )。
图3 将 A 座上2个盘子移到 B 座上(借助 C )
图4 将 B 座上2个盘子移到 C 座上(借助 A )
其中第2步可以直接实现。第1步又可用递归方法分解为:
1.1 将 A 上1个盘子从 A 移到 C ;
1.2 将 A 上1个盘子从 A 移到 B ;
1.3 将 C 上1个盘子从 C 移到 B 。
第3步可以分解为:
3.1 将 B 上1个盘子从 B 移到 A 上;
3.2 将 B 上1个盘子从 B 移到 C 上;
3.3 将 A 上1个盘子从 A 移到 C 上。
将以上综合起来,可得到移动3个盘子的步骤为:
A→C, A→B, C→B, A→C, B→A, B→C, A→C.
共经历7步。由此可推出:移动n个盘子要经历 2 n − 1 2^n-1 2n−1步。如移4个盘子经历15步,移5个盘子经历31步,移64个盘子经历 2 64 − 1 2^{64}-1 264−1步。
由上面的分析可知:将n个盘子从A座移到C座可以分解为以下3个步骤:
(1) 将A上 n-1个盘借助C座先移到B座上;
(2) 把A座上剩下的一个盘移到C座上;
(3) 将n-1个盘从B座借助于A座移到C座上。
上面第(1)步和第(3)步,都是把n-1个盘从一个座移到另一个座上,采取的办法一样的,只是座的名字不同而已。为使之一般化,可以将第(1)步和第(3)步表示为:将“one”座上n-1个盘移到“two”座(借助“three”座)。只是在第(1)步和第(3)中,one、two、three 和A、B、C的对应关系不同。对第(1)步,对应关系是one对应A,two对应B,three对应C。对第(3)步,是:one对应B, two对应C, three对应A。
因此,可以把上面3个步骤分成两类操作:
(1)当n==1时,直接将1个盘子从一个座上移到另一座上。
(2)当n>1时,将n-1个盘从一个座移到另一个座上。它是一个递归的过程(又可以拆分成3大步骤)。
下面编写程序,分别用两个函数实现以上的两类操作。
对应的函数定义如下。
void move(int no, char from, char to) {
cout << "将" << no << "号盘子从" << from << "移动到" << to << endl;
}
// 将n个碟子从p1挪动到p3, p2是中间的柱子
void hanoi(int n, char p1, char p2, char p3) {
if(n==1) {
move(n, p1, p3);
} else {
//将p3看作中间柱子,将n-1个碟子从p1移动到p2
hanoi(n-1, p1, p3, p2);
//将编号为n的盘子从p1移动到p3
move(n, p1, p3);
//将p1看作中间柱子,将n-1个碟子从p2移动到p3
hanoi(n-1, p2, p1, p3);
}
}
① 将n-1个盘子从A移动到B
图5 将n-1个盘子从A移动到B
② 将编号为n的盘子从A移动到C
图6 将编号为n的盘子从A移动到C
③ 将n-1个盘子从B移动到C
图7 将n-1个盘子从B移动到C
步骤①③ 明显是个递归调用
所以,Hanoi(汗诺)塔问题完整的递归实现代码如下。
#include
using namespace std;
void move(int no, char from, char to) {
cout << "将" << no << "号盘子从" << from << "移动到" << to << endl;
}
//将n个碟子从p1挪动到p3, p2为中间的柱子
void hanoi(int n, char p1, char p2, char p3) {
if(n==1) {
move(n, p1, p3);
} else {
//将p3看作中间柱子,将n-1个碟子从p1移动到p2
hanoi(n-1, p1, p3, p2);
//将编号为n的盘子从p1移动到p3
move(n, p1, p3);
//将p1看作中间柱子,将n-1个碟子从p2移动到p3
hanoi(n-1, p2, p1, p3);
}
}
int main() {
hanoi(3, 'A', 'B', 'C');
return 0;
}
当n==3时,程序输出如下。
将1号盘子从A移动到C
将2号盘子从A移动到B
将1号盘子从C移动到B
将3号盘子从A移动到C
将1号盘子从B移动到A
将2号盘子从B移动到C
将1号盘子从A移动到C
阅读下面的代码,给出代码运行的结果。
#include
using namespace std;
int x, y;
int fun1(int s) {
int x=10;
y=x*s;
return x+y;
}
int main() {
int n;
cin >> n;
cout << "fun1(" << n << ")=" << fun1(n) << endl;
cout << "x=" << x << ", y=" << y << endl;
return 0;
}
代码运行输出为
5
fun1(5)=60
x=0, y=50
#include
using namespace std;
int max(int x1, int x2, int x3) {
if(x1>x2 && x1>x3) return x1;
else if(x2>x1 && x2>x3) return x2;
else return x3;
}
int main() {
int x, y, z;
int biggest, m, i=1;
cin >> x >> y >> z;
biggest = max(x, y, z);
while(true) {
m = biggest * i;
if(m%x==0 && m%y==0 && m%z==0) break;
i++;
}
cout << m << endl;
return 0;
}
运行程序,输入
2 4 8
会输出
8
有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第3个人,又说比第2个人大2岁。问第2个人,说比第1个人大2岁。最后问第1个人,他说是10岁。请问第5个人多大。
显然,这是一个递归问题。
要求第5个人的年龄,就必须先知道第4个人的年龄,而第4个人的年龄也不知道,要求第4个人的年龄必须先知道第3个人的年龄,而第3个人的年龄又取决于第2个人的年龄,第2个人的年龄取决于第1个人的年龄。而且每一个人的年龄都比其前1个人的年龄大2。即:
age(5)= age(4)+2
age(4)= age(3)+2
age(3)= age(2)+2
age(2)= age(1)+2
age(1)=10
可以用数学公式表述如下:
a g e ( n ) = { 10 n = 10 a g e ( n − 1 ) + 2 n > 1 age(n)= \begin{cases} \ 10 & n=10\\ \ age(n-1)+2 & n>1 \end{cases} age(n)={ 10 age(n−1)+2n=10n>1
公式本身就是一个递归式,过程图示如图8所示:
图8 求第5个人的年龄
据此,可以直接给出C++的递归实现。
#include
using namespace std;
int age(int n) {
if(n==1) return 10;
else return age(n-1)+2;
}
int main() {
int n=5;
cout << age(n); // 18
return 0;
}
见前面相关内容。
斐波那契数列数值为:1, 1, 2, 3, 5, 8, 13, 21, 34, …在数学上,这一数列以如下递推的方法定义:F(0)=1, F(1)=1, F(n)=F(n-1)+F(n-2)(n≥2,n∈N),即
f ( n ) = { 0 n = 0 1 n = 1 f ( n − 1 ) + f ( n − 2 ) n > 1 f(n)= \begin{cases} \ 0 & n=0\\ \ 1 & n=1\\ \ f(n-1)+f(n-2) & n>1 \end{cases} f(n)=⎩ ⎨ ⎧ 0 1 f(n−1)+f(n−2)n=0n=1n>1
帕斯卡三角形,是一个三角形矩阵,其顶端是 1,视为(row0)。第1行(row1)(1&1)两个1,这两个1是由他们上头左右两数之和 (不在三角形内的数视为0)产生。依此类推产生第2行(row2): 0+1=1; 1+1=2; 1+0=1。第3行(row3): 0+1=1; 1+2=3; 2+1=3; 1+0=1。循此法可以产生其余行元素——即 ( a + b ) n (a+b)^n (a+b)n的展开式中的各项系数(n=0表示杨辉三角的第0行row0)。
自定义函数YanghuiTriangle(a[][7])输出行数N=7的杨辉三角,如图9所示。
图9 输出的行数N=7的杨辉三角