什么是递归:程序调用自身的编程技巧称为递归(recursion)。递归作为一种算法在程序设计语言中普遍应用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它一般把一个大型复杂的问题层层转化为一个与原问题类似的规模较小的问题来求解,递归策略只需少许的程序就可描述出解题过程所须要的屡次重复计算,大大地减小了程序的代码量。git
递归的主要思考方式在于 : 把大事化小
注意:函数在调用的时候会向内存申请空间。(递归的过程就是函数的不停调用过程)算法
(2)递归的两个必要条件编程
- 存在限制条件,当知足这个限制条件的时候,递归便再也不继续。
- 每次递归调用以后愈来愈接近这个限制条件。
(3)最简单的递归函数:主函数调用主函数数组
#include<stdio.h> int main() { printf("hello world\n"); main(); return 0; }
↑↑↑上述代码结果:死循环,栈溢出(stack overflow)ide
为何会出现栈溢出呢?
要想搞懂栈溢出的缘由,首先咱们要明白程序运行过程当中内存的划分分区函数
每一次函数的调用,都须要在栈区分配必定的空间(也就是说函数调用也在栈区开辟空间),调用次数太多,栈空间不够分配(被耗干),致使栈溢出。测试
接受一个整型值(无符号),按照顺序打印它的每一位。spa
例如︰输入∶1234,输出1234.
要顺序打印它的每一位,就须要获得它的每一位,1234最容易获得的就是个位
1234 % 10 = 4
1234 / 10 = 123 % 10 = 3
123 / 10 = 12 % 10 = 2
12 / 10 = 1 % 10 = 1
1 / 10 = 0设计
#include<stdio.h> void print(int n) { if (n > 9) { print(n / 10); } printf("%d ", n % 10); } int main() { unsigned int num = 0; scanf("%d", &num);//1234 print(num);//打印1 2 3 4 return 0; //print(1234) //print(123) 4 //print(12) 3 4 //print(1) 2 3 4 // 1 2 3 4 //当()里面的数字大于9的时候就拆分,小于9,为个位数的时候中止拆分,进行打印 }
比较上面两个递归函数,咱们能够看到: 递归的两个必要条件
①存在限制条件,当知足这个限制条件的时候,递归便再也不继续。
②每次递归调用以后愈来愈接近这个限制条件。3d
注意:这两个条件是必要条件,不是充分条件,也就是说递归函数必定知足这两个条件,可是知足这两个条件不必定是递归。
看下面这个例子:
按F10进行调试:
每一次函数的调用,都须要在栈区分配必定的空间,调用次数太多,栈空间不够分配(被耗干),致使栈溢出。
因此咱们在写递归代码的时候,必定要注意如下几点:
一、不能死递归,要有跳出条件,每次递归逼近跳出条件
二、递归层层不能太深
编写函数不容许建立临时变量,求字符串的长度。
若是调用临时变量:若是用strlen算:
使用递归以下⬇⬇⬇⬇⬇⬇
初步解题思路:用了临时变量count
#include<stdio.h> #include<string.h> //求字符串长度 int my_strlen(char* pr) { int count = 0; while (*pr != '\0') { count++; pr++; } return count; } int main() { char arr1[] = "bit"; //int len = strlen(arr1);//求字符串长度 int len = my_strlen(arr1); //arr1是数组,数组传参,传过去的不是整个数组,而是首元素的地址 printf("%d\n", len); return 0; }
这种方式虽然计算出字符串的长度,可是建立了一个临时变量count,如今使用递归的方式来实现:
int my_strlen(char* pr) { if (*pr != '\0') return 1 + my_strlen(pr + 1); else return 0; } //把大事化小 //my_strlen("bit") //1+my_strlen("it") //1+1+my_strlen("t") //1+1+1+my_strlen("\0") //1+1+1+0 = 3
画图详解:
求n的阶乘。(不考虑溢出)
#include<stdio.h> #include<string.h> int Fun1(int x)//迭代(循环)实现 { int i = 0; int y = 1; for (i = 1; i <= x; i++) { y = y * i; } return y; } int Fun2(int x)//递归实现 { if (x > 1) return x * Fun2(x - 1); else return 1; } int main() { int n = 0; int ret = 0; printf("请输入一个数:>>\n"); scanf("%d", &n); //ret = Fun1(n);//循环的方式 ret = Fun2(n);//递归的方式 printf("%d的阶乘为:%d\n", n, ret); return 0; }
求第n个斐波那契数。(不考虑溢出)
介绍斐波那契数列,斐波那契数列的排列是:1 , 1,2,3,5,8,13,21,34,55,89,......
依次类推下去,你会发现,它后一个数等于前面两个数的和。
#include<stdio.h> int count = 0; int Fib1(int x)//递归实现 { if (x == 3) count++; if (x <= 2) return 1; else return Fib1(x - 1) + Fib1(x - 2); } int Fib2(int x)//函数实现 { int a = 1; int b = 1; int c = 1; while (x > 2) { c = a + b; a = b; b = c; x--; count++; } return c; } int main() { int n = 0; int ret = 0; scanf("%d", &n); //ret = Fib1(n);//求第n个斐波那契数 ret = Fib2(n);//循环实现 printf("%d\n", ret); printf("count = %d\n", count); return 0; }
咱们在主函数里面写后续要被调用的某个函数的时候,先假设要用这个函数实现什么功能,直接去用,以后再去真正定义并实现这个函数。
这种思想叫作:TDD - 测试驱动开发 先去想函数怎么用,而后再实现。
递归结果:
递归方式:效率低
循环结果:
能够看出此时使用递归方式并不高效,其计算同一个数好比 3 的次数为 2 ^ n (递归方式)
而使用循环方式(n > 3时) 次数为 n
那咱们如何改进呢 ?
在调试factorial函数的时候,若是你的参数比较大,那就会报错 : stack overflow(栈溢出)这样的信息。系统分配给程序的栈空间是有限的,可是若是出现了死循环,或者(死递归),这样有可能致使一直开辟栈空间,最终产生栈空间耗尽的状况,这样的现象咱们称为栈溢出。
那如何解决上述的问题 ?
1.将递归改写成非递归。
⒉使用static对象替代nonstatic局部对象。在递归函数设计中,可使用static对象替代nonstatic局部对象(即栈对象),这不只能够减小每次递归调用和返回时产生和释放nonstatic对象的开销,并且static对象还能够保存递归调用的中间状态,而且可为各个调用层所访问。
一、字符串逆序:
编写一个函数 reverse_string(char* string)(递归实现)
实现:将参数字符串中的字符反向排列,不是逆序打印。
要求:不能使用C函数库中的字符串操做函数。
好比 : char arr[] = “abcdef”,逆序以后数组的内容变成:fedcba
#include<stdio.h> //#include<string.h> //int my_strlen(char* str)//非递归方法求字符串长度 //{ // int count = 0; // while (*str != '\0') // { // count++; // str++; // } // return count; //} int my_strlen(char* str)//递归方法求字符串长度 { if (*str != '\0') return 1 + my_strlen(str + 1); else return 0; } //void reverse_string(char* str)//非递归方法逆序字符串 //{ // //int len = strlen(str); // int len = my_strlen(str); // int left = 0; // int right = len - 1; // while (left < right) // { // char tmp = *(str+left); // *(str + left) = *(str + right); // *(str + right) = tmp; // left++; // right--; // } // //} void reverse_string(char* str) { int len = my_strlen(str); if (len > 1) { char tmp = *str; *str = *(str + len - 1); *(str + len - 1) = '\0'; reverse_string(str + 1); *(str + len - 1) = tmp; } } int main() { char arr[] = "abcdefjh"; printf("逆序前:%s\n", arr); reverse_string(arr); printf("逆序后:%s\n", arr); return 0; }
写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
例如,调用DigitSum(1729),则应该返回1 + 7 + 2 + 9,它的和是19
例如输入:1729,输出:19
思路:大事化小,个位数的9最容易拿下来
#include<stdio.h> int DigitSum(int n) { if (n > 9) { return DigitSum(n / 10) + n % 10; } else { return n; } } int main() { int num = 1729; int sum = DigitSum(num); printf("%d\n", sum); return 0; }
编写一个函数实现n的k次方,使用递归实现。
#include<stdio.h> double my_pow(int n, int k) { if (k > 0) { return n * my_pow(n, k - 1); } else if (k == 0) { return 1; } else { return 1.0 / my_pow(n, -k); } } int main() { int n = 0; int k = 0; double ret = 0.0; scanf("%d%d", &n, &k); ret = my_pow(n, k); printf("%lf\n", ret); return 0; }
递归的扩展:
经典题目:
一、汉诺塔问题
二、青蛙跳台阶问题