目录
一.知识点
1.基本概念
程序调用自身的编程技巧称为递归( recursion)。 递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的 一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略 只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。 递归的主要思考方式在于:把大事化小。
2.递归的两个必要条件
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条件。
二.基本题目
1.计算一个数的每位之和(递归实现)
写一个递归函数DigitSum(n),输入一个非负整数,返回组成他的数字之和
代码:
int DigitSum(unsigned int n)
{
if (n > 9)
{
return DigitSum(n / 10) + n % 10;
}
else
return n;//最后结束只剩个位,然后开始返回
}
int main()
{
unsigned int n = 0;
scanf("%u", &n);
int ret=DigitSum(n);
printf("ret=%d\n", ret);
return 0;
}
将%10 /10 再加和转化为递归形式.
2.递归实现n的k次方
double K_min(int n, int k)
{
//结束条件
//n*n^(k-1)
if (k < 0)
return K_min(n, 1 / K_min(n, -k));//这段挺有意思,是k为负数递归的实现
else if (k == 0)
return 1;
else
return n * K_min(n, k - 1);//k为正数递归的实现
}
int main()
{
int n, k;
scanf("%d%d", &n, &k);
double ret = K_min(n, k);
printf("ret=%f\n", ret);
return 0;
}
请读者注意,递归的限制条件很重要,这时应该是最后一次递归然后开始返回.
3.正序打印一个数的每一位
void print(unsigned int n)
{
//大事化小,条件约束
if (n>9)
{
print(n/10);
}
//出来循环打印
printf("%d ", n%10);
//print(1234)
//print(123) 4
//print(12) 3 4
//print(1) 2 3 4
}
int main()
{
unsigned int input = 0;
scanf("%u", &input);
print(input);//接受一个无符号整型,然后按照顺序打印
return 0;
}
这个递归相对容易.
4.逆序打印一个数的每一位
void digit(unsigned int num)
{
if (num > 0)
{
printf("%d", num % 10);
digit(num / 10);
}
}
int main()
{
unsigned int num = 0;
scanf("%u", &num);
digit(num);
return 0;
}
这道题目还是很有意思的,它让笔者对递归有了更深的理解,原来笔者一直将打印放在if条件句外,但是在递归回归后会执行之后的命令,使得重复打印。重新设计打印在前面,就规避了这个问题。
5.不创建临时变量计算一个字符串的长度
//递归求解
//my_strlen("abc)
//1+my-strlen("bc)
//1+1+my_strlen(c)
//1+1+1+my_strlen("")
//1+1+1+0
int my_strlen(char* str)
{
if (*str != '\0')
//灵性的添加了一个1使得代码活络了起来
return 1 + my_strlen(str+1);//++str和str+1不相同,会导致str改变
else
return 0;
}
int main()
{
char arr[] = "abc";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
6.递归计算n的阶乘
int fac(int n)
{
if (n <= 1)
return 1;
else
{
return n*fac(n - 1);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret= fac(n);
printf("%d\n", ret);
return 0;
}
7.递归实现找到第n个斐波那契数
①.递归实现
递归题目里估计最著名的就是斐波那契数了,但是递归的写法其实并不高效,笔者进行了修改,使得各位可以看到它的弊端
//仅仅表达思想递归:
//斐波那契数列 1 1 2 3 5 8 13 21 34 55
int count = 0;
int Fib(int n)
{
if (n == 4)
count++;//计算重复度,重复度过大,时间浪费
if (n <= 2)
{
return 1;
}
else
return Fib(n - 1) + Fib(n - 2);//效率低是因为重复很多
}
int main()
{
//第n个斐波那契数
int n = 0;
scanf("%d", &n);
int fib = Fib(n);
printf("%d\n", fib);
printf("%d\n", count);
return 0;
}
第三个数就是笔者测试的(笔者输入的是4)重复计算的个数高达接近20万.可见这个递归实现有多么低效。而且仅仅对于第50个数就需要很长时间计算。可以说十分鸡肋。
②非递归
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 0;
while (n >= 3)
{
c = a + b;
a = b;
b = c;
n--;//进来几次,算几次
}
return c;
}
int main()
{
//第n个斐波那契数
int n = 0;
scanf("%d", &n);
int fib=Fib(n);
printf("%d\n", fib);
return 0;
}
这个非递归实现还是挺有意思的。笔者利用图解可以让大家更加理解。
8.青蛙跳台阶问题
青蛙每次可以选择跳一个或者两个台阶,那么青蛙要跳到第n个台阶需要多少种跳法呢?
我们可以简单分析一下:
那么代码就可以实现:
int fib(int n)
{
if (n <= 2)
return n;
else
{
return fib(n - 1) + fib(n - 2);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
printf("%d", fib(n));
return 0;
}
9.逆向输出字符串
逆向输出字符串
//常规实现
void reverse_string(char* str)
{
int count = 0;
char* arr = str;
while (*str != '\0')
{
count++;
str++;
}
int left = 0;
int right = count - 1;
while(left <= right)
{
char tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
递归实现:
#include<string.h>
void reverse_string(char* str)
{
int count = strlen(str);
char tmp = str[0];
str[0] = str[count - 1];
str[count - 1] = '\0';
if (strlen(str+1) >= 2)
{
reverse_string(str + 1);
}
str[count - 1] = tmp;
}
int main()
{
char arr[] = "qwertyu";
reverse_string(arr);
printf("%s\n", arr);
return 0;
}
这个递归是如何操作的呢?首先笔者认为要根据这个字符串思考常规实现是如何转化为递归实现。很明显,基本思想是俩俩交换。见图
10.计算排列数
输入两个数,计算A(n上)(m下)😂。
#include<stdio.h>
int arrange(int n,int m)
{
if(m)
{
return n*arrange(n-1,m-1);
}
else
return 1;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int ret=arrange(n,m);
printf("%d\n",ret);
return 0;
}
11.汉诺塔问题(Hanoi)
题目笔者就不多赘述了。
图解:
代码实现:
//汉诺塔问题
void move(char pos1, char pos2)
{
printf("%c->%c ", pos1, pos2);
}
void Hanoi(int n, char pos1, char pos2,char pos3)
{
if (n == 1)
{
move(pos1, pos3);
}
else
{
Hanoi(n - 1, pos1, pos3, pos2);
move(pos1, pos3);
Hanoi(n - 1, pos2, pos1, pos3);
}
}
int main()
{
//笔者为了直观展示结果,多加了几个,其实只需一个就可以了
int n,m,k;
scanf("%d", &n);
Hanoi(n, 'A','B', 'C');
printf("\n");
scanf("%d", & m);
Hanoi(m, 'A', 'B', 'C');
printf("\n");
scanf("%d", &k);
Hanoi(k, 'A', 'B', 'C');
return 0;
}
对比图一下:
可以发现是可以吻合的。但是注意汉诺塔问题n值不能过大,通过图解诸君已经可以发现它的执行也是比较繁琐的,需要2^n-1次,指数爆炸还是挺恐怖的。
通过上面几个简单的题目就可以发现递归的好处,化繁为简。笔者认为,递归本身是比较抽象的,建议诸君在做这类题目的时候可以通过画图的形式进行剖析,那么就比较好解。要注意递归的限制条件和它的循环是怎么设计去接近这个条件,这一点是比较重要的。
但是递归也是有它的弊端:
递归层数太深会出现栈溢出的。
解决方案
1.递归改为非递归
2.非静态改为静态//不在栈上挪到静态区。
好的,本次分享就到这里了。🌹