一、递归是什么?
程序调用自身的编程技巧称为递归:一个过程或函数在其定义或说明中有直接或者间接调用自身的一种方法。
他通常把一个大型复杂的问题转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的两个条件:
(1).存在限制条件,当满足这个限制条件的时候,递归便不再继续。
(2).每次递归调用之后越来越接近这个限制条件。
二、递归的实例
1.按照顺序打印一个整数的每一位
void print(unsigned int n)
{
if(n>9)
{
print(n/10);
}
printf("%d",n%10);
}
int main()
{
unsigned int num=0;
scanf("%u",&num);//1234
print("num");
return 0;
}
如输入1234 输出结果为 :1 2 3 4
2.递归中容易出现的问题
1.死循环
int main()
{
print("hello world");
main()
}
程序会先打印"hello world",然后调用自己,不断的打印"hello world",直到栈区内存全部用完。
2.栈溢出
栈溢出是在使用递归时容易出现的问题。首先我们先了解内存的结构。内存包含栈区、堆区、静态区。栈区储存着局部变量 ,调用函数的返回值等临时变量
每一个函数在调用时,都会在栈区申请一个空间(栈帧空间)。每调用一次,栈区开辟一次。当递归调用的次数太多的时候,栈区不够导致的现象,就被称为栈溢出。当递归结束的时候,栈区一层一层的销毁占用的空间,直到程序返回主函数。
这也说明了为什么递归一定要有限制的条件并且每次递归调用之后越来越接近这个限制条件—递归受到栈区大小的限制。
当然在c语言中我们还有其他的解决方案,而在这里我们只是初始递归。
3.递归中的经典问题
1.模拟strlen函数
strlen是求字符串的长度,但不包括字符串的null结束符。 strlen为C语言库函数,包含于string.h中,作用为计算一个字符串 (字符数组)中元素的个数,即从数组头部计数,直到遇到字符串结束符\0为止,计数结果不包括\0。
int my_strlen2(char* str)
{
if("*str!='\0'")
return 1+my_strlen2(str+1);
else
return 0;
}
2.斐波那契数列与青蛙跳台阶问题
(1)什么是斐波那契数列
斐波那契数列,又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34......也就是前面两项数的和等于第三项数的值。
(2)斐波那契数列的c语言实现
int fib(int n)
{
if(n<=2)
return 1;
else
{
return fib(n-1)+fib(n-2);
}
}
当然,我们也不是一定要使用递归来实现,使用循环一样可以解决问题。
int fib(int n)
{
int a=1;
int b=1;
int c=1;
while(n>2)
{
c=a+b;
a=b;
b=c ;
n--;
}
return c;
}
3.青蛙跳台阶问题
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
我们可以这样来想:
假设青蛙需要跳上n阶的台阶;那么青蛙需要先跳上第n-1个台阶,最后再跳上到第n阶。或者跳到第n-2个台阶再跳两个台阶(跳一个台阶的情况已经包含在第一种情况里,不再考虑)。是不是和斐波那契数列很像?
F(N)=F(N-1)+F(N-2)
int jump(int n)
{
if(1==n)
{
return 1;
}
else if(2==n)
{
return 2;
}
else
return jump(n-1)+jump(n-2);
}
int main()
{
int num=0;
scanf_s("%d", &num);
int ret = jump(num);
printf("%d", ret);
return 0;
}
同样我们也可以使用循环来解决青蛙跳台阶的问题,读者可以自己尝试,博主不再赘述。
4.汉诺塔问题
A柱上有N个从小到大放置的盘子,B与C上没有盘子。目标是要把所有的盘子从最左边(柱子A)移动到最右边(柱子C),条件是
(1)每次只能移动一个盘子(2)小盘子只能放在大盘子之上
我们从最简单的情况开始思考:假设只有一个盘子。我们直接把盘子从A拿到C;到两个盘子的情况了,我们把A上面的小盘子放在B,然后把大盘子放在C,最后把B的盘子放回C;三个盘子的情况也类似,我们需要先把A上面两个盘子先放到B上面,然后把最后一个盘子放在C,最后把B的两个盘子放在C上面......
那如何处理N个盘子的情况?我们把A柱上的n个盘子移到c柱上面去,一个需要三步:
(1).把n-1个盘从A移到B什么去 (2).把第n个盘从A移到c上面去 (3).把B上面的n-1个盘移到c上面去。
我们每一次做的动作都是类似的,只是执行的次数不一样而已。所以我们可以设计出以下的递归程序。
#include<stdio.h>
void move(char a, char b)
{
static int i = 0;
i++;
printf("第%d步,移动%c到%c去\n", i, a, b);
}
void Hannoi(int n, char cm1, char cm2, char cm3)
{
if (1 == n)
{
move(cm1, cm3);
}
else
{
Hannoi(n - 1, cm1, cm3, cm2);//调用函数,把N-1个盘子从第一个柱子移动到第二个柱子
move(cm1, cm3);//把最后一个盘子从第一个移到第三个
Hannoi(n - 1, cm2, cm1, cm3);//调用函数,把N-1个盘子从第二个柱子移动到第三个柱子
}
}
int main()
{
int n;
printf("请输入盘的数量:");
scanf("%d", &n);
Hannoi(n, 'a', 'b', 'c');
return 0;
}
5.计算N的K次方
int pow(int n,int k)
{
if(k==0)
return 1;
else if(k>0)
return n*pow(n,k-1);
else
return 1.0/(pow(n,-k));
}
int main()
{
int n=0;
int k=0;
scanf("%d %d",&n,&k);
int ret=pow(n,k);
printf("%d ",ret);
}
总结
递归在程序设计中是一种重要的方式,在程序设计中被广泛使用。我们还可以使用递归实现二进制转化、倒序排列等问题,博主在文章中不一一举例。但是递归的核心思想就是把一个复杂任务,简化成大量的小的任务。每一次调用完成当前的任务,然后逐层进行,完成下一个阶段的小任务。
博主文章的内容主要是自学C过程中记录的一些笔记。如果以后有机会,我还会继续把其他笔记用博客的方式记录下来。文章中的不足处,还望各位读者斧正。