递归
1.递归的定义
一个函数自己直接或间接的调用自己
程序调用自身的编程技巧称为递归。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
2.不同函数之间的调用
调用函数和被调用函数之间的链接及信息交换需要通过栈来进行
-
当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需先完成3件事
- 将所有的实在参数、返回地址等信息传递给被调用函数保存;
返回地址是下一句代码地址,这样在调用函数结束,直接返回下一句代码地址,进行继续运行
-
为被调用函数的局部变量分配存储区;
-
将控制转移到被调函数的入口。
-
而从被调用函数返回调用函数之前,系统也应完成3件工作:
- 保存被调函数的计算结果;
- 释放被调函数的数据区;
- 依照被调函数保存的返回地址将控制转移到调用函数。
当有多个函数构成嵌套调用时,按照“后调用先返回”的原则,上述函数之间的信息传递和控制转移必须通过“栈”来实现,即系统将整个程序运行时所需的数据空间安排在一个栈中,每当调用一个函数时,就为它在栈顶分配一个存储区,每当从一个函数退出时,就释放它的存储区,则当前正运行的函数的数据区永远在栈顶。
#include <stdio.h>
void a();
void b();
void c();
int main()
{
a(); //运行a
//这个函数就是在主函数调用函数a,然后在函数a调用函数b,再在函数b调用函数c
return 0;
}
void a()
{
printf("运行a\n");
b(); //运行b
printf("111\n"); //当运行完上一行代码,才会执行这一行
}
void b()
{
printf("运行b\n");
c(); //运行c
printf("222\n");
}
void c()
{
printf("运行c\n");
printf("333\n");
}
/*
运行结果
运行a
运行b
运行c
333
222
111
*/
3.递归——函数自己调用自己
在计算机看来,函数自己调用自己和函数调用其他函数是一样的,只不过是我们日常的思维方式理解比较不同而已
自己调用自己也是每次调用都重新分配地址,然后调用完保存返回值,再重新进行调用,分配地址
也就是说,如果一个函数自己调用自己5次,并不是分配五个地址,而是分配一个,每次调用进行分配,调用完进行释放,然后下一次调用再分配
#include <stdio.h>
void a();
int main()
{
a(); //运行a
//这个函数就是在主函数调用函数a,然后在函数a中继续调用函数a
return 0;
}
void a()
{
printf("运行a\n");
a(); //调用函数a
}
/*
运行结果
运行a
运行a
。。。
*/
//因为会一直调用函数a,死循环,会一直循环下去
因为这样会一直死循环下去,所以在进行递归调用的时候会边界条件,当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
#include <stdio.h>
void a(int x);
int main()
{
a(6); //运行a
//这个函数就是在主函数调用函数a,然后在函数a中继续调用函数a,添加了边界条件,
//当x=0时退出递归,并且在函数a中进行x的递减,从而达到边界条件
return 0;
}
void a(int x)
{
if (x == 0) //当x等于0的时候退出递归
{
printf("递归结束");
}
else
{
printf("运行a\n");
a(x - 1); //调用函数a
}
}
/*
运行结果
运行a
运行a
运行a
运行a
运行a
运行a
递归结束
*/
4.递归满足的三个条件
(1)递归必须得有一个明确的终止条件
(2)该函数所处理的数据规模必须在递减
(3)这个转化必须是可解的
5.循环和递归的关系
(1)递归的优缺点
易于理解
速度慢
存储空间大
(2)循环的优缺点
不易理解
速度块
存储空间小
(3)累加的递归实现和循环实现
#include <stdio.h>
int a(int x)
{
if (x == 1)
return x;
else
return a(x - 1) + x;
}
int main()
{
//递归实现
int val = a(100);
printf("1+2+。。。+100=%d\n", val);
//循环实现
int val;
for(int i = 1;i <= n;i++)
{
val += i;
}
printf("1+2+。。。+100=%d\n", val);
return 0;
}
6.递归的应用
(1)树和森林就是以递归的方式定义的
(2)树和图的很多算法都是递归来实现的
(3)很多数学公式就是以递归的方式定义的
比如斐波那契序列
递归实现
1.累加和
#include <stdio.h>
int a(int x);
int main()
{
int val = a(100);
printf("1+2+。。。+100=%d\n", val);
return 0;
}
int a(int x)
{
if (x == 1)
{
return x;
}
else
{
return a(x - 1) + x;
}
}
/*
输出结果:
1+2+。。。+100=5050
*/
2.求阶乘
#include <stdio.h>
int a(int x);
int main()
{
int val = a(5);
printf("5!=%d\n", val);
return 0;
}
int a(int x)
{
if (x == 1)
{
return x;
}
else
{
return a(x - 1) * x;
}
}
/*
输出结果:
5!=120
*/
3.排序
输入十个数,进行排序,并打印最大和最小值
#include <stdio.h>
int px(int* a, int index, int size) {
int i, n;
if (index == size - 1)
return 1;
else {
for (i = index + 1; i < size; i++) {
if (a[index] > a[i]) {
n = a[index];
a[index] = a[i];
a[i] = n;
}
}
return px(a, ++index, size);
}
}
int main()
{
int a[10];
for (int i = 0; i < 10; i++)
scanf_s("%d", &a[i]);
int size = sizeof(a) / sizeof(a[0]);
px(a, 0, size);
for (int i = 0; i < 10; i++)
{
printf("%d,", a[i]);
}
printf("\n最小值%d\n", a[0]);
printf("\n最大值%d\n", a[size - 1]);
}
/*
输出结果:
1
2
4
3
6
8
7
9
5
55
1,2,3,4,5,6,7,8,9,55,
最小值1
最大值55
*/
4.汉诺塔
源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
问题:将A上的盘子借助B移动到C
(1)伪算法:
if(n>1)
{
先把A柱子上的n-1个盘子从A借助C移到B
将A柱子上的第n个盘子直接移到C
再将B柱子上的n-1个盘子借助A移到C
}
(2)具体实现
#include <stdio.h>
void Hanoi(int n, char a, char b, char c); //将A上的盘子借助B移动到C
void Move(int n, char a, char b);
int count; //全局变量,运行次数
int main()
{
int n; //盘子的个数
printf("请输入要移动盘子的数量:");
scanf_s(" %d", &n);
Hanoi(n, 'A', 'B', 'C');
return 0;
}
void Hanoi(int n, char a, char b, char c) //将A上的盘子借助B移动到C
{
if (n == 1)
{
Move(n, a, c);
}
else
{
Hanoi(n - 1, a, c, b); //先把A柱子上的n-1个盘子从A借助C移到B
Move(n, a, c); //将A柱子上的第n个盘子直接移到C
Hanoi(n - 1, b, a, c); //再将B柱子上的n-1个盘子借助A移到C
}
}
void Move(int n, char a, char b)
{
count++;
printf("第%d次移动 :移动 %d: 从 %c 移动到 %c\n", count, n, a, b);
}
5.斐波那契数
波纳契数列以如下被以递归的方法定义:
F0=0,F1=1,Fn=F(n-1)+F(n-2)(n>=2,n∈N*)
这个数列从第3项开始,每一项都等于前两项之和
#include<stdio.h>
int fun(int n)
{
if (n <= 1)
return n;
else
return fun(n - 1) + fun(n - 2);
}
int main()
{
int n;
printf("请输入要输出多少项斐波那契数列:");
scanf_s("%d", &n);
int i;
for(i = 0; i < n + 1; i++)
{
printf("%d, ", fun(i));
if (i != 0 && i % 5 == 0) //每五项进行一次换行
printf("\n");
}
printf("第 %d 项是:%d\n", n, fun(n));
return 0;
}