一.认识递归:
- 概念:程序调用自身的编程技巧称为递归
- 主要思考方式:大事化小
- 定义:递归作为一种算法在程序设计语言中广泛应用,一个过程或函数在其定义或说明中有直接或间接调用自身的方法。它通常把一个大型复杂问题层层转化为一个与原问题相似规模较小的问题来求解。
二.递归的优缺点
- 优点:只需要少量的程序就可以描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量
- 缺点:如果递归层次台深容易造成死递归,最后导致程序崩溃;有时候递归的效率会很低。
三.函数递归的两个限制条件:
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后越来越接近这个限制。
四.例题:
试题一:接收一个整型值(无符号),按照顺序打印它的每一位
例如:输入1234,打印1 2 3 4
用代码实现:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void print(unsigned int n)
{
if (n > 9)//限制条件
{
print(n / 10);
}
printf("%d ", n % 10);
//%d是打印有符号的整数(会有正负数)
//%u是打印无符号的整数
}
int main()
{
unsigned int i = 0;
scanf("%u", &i);
print(i);
//接收一个整型值(无符号),按照顺序打印它的每一位
return 0;
}
运行结果:
.
试题二:编写函数(不允许创建临时变量),求字符串的长度
扩展知识点:指针变量的自增运算(初步了解)
-
数组指针是指数组的开始地址(首地址),数组元素的指针就是数组元素的地址。
-
a[3]=6;下标法引用数组元素,也可以用指针引用数组元素。
int a[5]={5,6,7,8,9};能引用的是0--4,只要是数组,那么数组元素的内存一定是紧挨着的,从首地址开始以数据类型所占字节进行叠加。
-
数组名a代表的是数组的首地址,即数组名a等于数组中首元素的地址,a==&a[0]
-
假如现在p指向数组首地址,a是数组名(代表数组首地址),那么p+i 或者 a+i 就是数组元素a[i]的地址,也就是她们指向了数组a的第i个元素。 相当于,p+3为&a[3]. 结论:p+i 或者 a+i 都是地址,可以赋给指针变量。
-
*p++:
优先级相同,并且都是从右到左的结合性,所以等价于*(p++)
p++为先用后加,所以*(p++)整个作用是:得到p指向的变量的值(*p),然后再使p指针自增,指向下一个数组元素。
printf("%d\n",*p++);先打印出a[0]的值,然后p指向了a[1]。 -
*++p:
相当于*(++p),和第六不同,++p是先加后用,p会先自增1,;然后再去用p值。
printf("%d\n",*++p);p指向了a[1],打印a[1]的值。 -
(*p)++:
表示p所指向的元素加1,如果p指向数组首地址,那就等价于a[0]++;实际上是数组元素值+1,不是指针+1.
用代码实现:
(1)不符合题意的写法(循环):
//用循环完成(不符合题意)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int my_strlen(char* str)
{
int count = 0;
while (*str != '\0')//这里的'\0'是终止符号
{
count++;
str++;//找下一个字符
}
return count;
}
int main()
{
char arr[] = "study";//[s t u d y \0]
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
(2)符合题意的写法(递归):
//符合题意的写法
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int my_strlen(char* arr)
{
if (*arr != '\0')
{
return 1 + my_strlen(arr+1);
}
else
{
return 0;
}
}
int main()
{
char arr[] = "study";
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
运行结果:
.
五.迭代与递归比较:
- 概念:利用变量的原值推算出变量的一个新值,如果递归是自己调用自己的话,迭代就是A不停的调用B。
- 递归中一定有迭代,但是迭代中不一定有递归,大部分可以相互转换.能用迭代的不用递归,递归调用函数,浪费空间,并且递归太深容易造成堆栈的溢出。
- 递归和迭代都是循环的一种。
简单地说,递归是重复调用函数自身实现循环。迭代是函数内某段代码实现循环,而迭代与普通循环的区别是:循环代码中参与运算的变量同时是保存结果的变量,当前保存的结果作为下一次循环计算的初始值。
六.例题:
试题三:求n的阶乘(不考虑溢出)
非递归写法(迭代):
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int fac(int a)
{
int i = 1;
int sum = 1;
while (i <= a)
{
sum = sum * i;
i++;
}
return sum;
}
int main()
{
int n = 0;
scanf("%d", &n);
int F = fac(n);
printf("%d", F);
return 0;
}
递归写法:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int fac(int a)
{
if (a <= 1)
return 1;
else
return a * fac(a - 1);
}
int main()
{
int n = 0;
scanf("%d", &n);
int F = fac(n);
printf("%d", F);
return 0;
}
.
试题四:求第n个斐波那契数(不考虑溢出)
斐波那契数列:1,1,2,3,5,8,13,21,34,55....
递归写法:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int fib(int n)
{
if (n <= 2)
return 1;
else
return fib(n - 1) + fib(n - 2);
}
int main()
{
int i = 0;
scanf("%d", &i);
int F = fib(i);
printf("%d", F);
return 0;
}
此时我们发现光标一直闪烁,说明此时计算机在努力计算
.
非递归写法:(可迅速得出结果)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int i = 0;
scanf("%d", &i);
int F = fib(i);
printf("%d", F);
return 0;
}
总结:
- 递归:可以减少程序的代码量,但是如果数字太大容易造成死循环,而且效率很低
- 非递归:虽然代码量多,但是效率却很高
七.提示:
- 许多问题是以递归的形式进行解释的,这只是因为它比非递归形式更为清晰。
- 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
八.函数递归的几个金典题目:
例题一:汉诺塔问题
(后续会去解决)
例题二:青蛙跳台阶问题