一. 递归
程序员调用自身的编程技巧称为递归。递归作为一种算法在程序设计语言中广泛应用。
1.一个过程或函数在其定义或说明中有直接或者间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可以描述出解题过程所需要的多次重复计算,大大的减少了程序的代码量。
2.递归的主要思考方式在于:大事化小
二.递归的两个必要条件
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续
2.每次递归调用之后越来越接近这个限制条件
例如:
练习1:
//接收一个整形值(无符号),按照顺序打印它的每一位,例如:输入1234,输出1 2 3 4
//解题思路:接收了一个1234,然后你先%10得到个位数4,然后在/10去掉个位数4,得到123,然后不断重复这个过程,在把这几个数字4 3 2 1 存起来,倒着打印出来。方法是可以的但是太过麻烦
//Print(123) 4
//Print(12) 3 4
//Print(1) 2 3 4 一位数字就没有必要继续拆下去了
void Print(int n)
{
if (n > 9) //存在限制条件
{
print(n / 10);
}
printf("%d ", n % 10); //无限接近结果
}
int mian()
{
unsigned int num = 0;
scanf("%d", num);
Print(num);
return 0;
}
练习2:
编写函数不允许创建临时变量,求字符串的长度 (相当于自己写一个strlen的函数来求解字符串的长度)
正常的求解方法:
①
int main()
{
char arr[] = "bit";
int len = strlen(arr);
printf("%d\n", len);
return 0;
}
②
//你会发现这个还是无法满足题目所说的要求,因为你写的函数内部创建了一个count的变量
int my_strlen(char* str)
{
int count = 0;
while(*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "bit";
int len = my_strlen(arr);
printf("len = %d\n", len);
return 0;
}
使用递归的方法来求解字符串的长度
int my_strlen(char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "bit";
int len = my_strlen(arr);
printf("len = %d\n", len);
return 0;
}
//大事化小
//my_strlen("bit")
//1 + my_strlen("it")
//1 + 1 + my_strlen("t")
//1 + 1 + 1 + my_strlen("")
//1 + 1 + 1 + 0
//3
//*str != '\0' 是限制条件,当不满足时就会不再进行递归,结果在不断地接近目标
练习3:求n的阶乘
1.循环的方式实现
int Fac1(int n)
{
int i = 0;
int ret = 1;
for (i = 1; i <= n; i++) //n! 不好想我们可以倒着思考问题 1*2*3 ....*n
{
ret *= i;
}
return ret;
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = Fac1(n); //循环的方式
printf("%d\n", ret);
}
int Fac2(int n)
{
if (n <= 1)
return 1;
else
return n*Fac2(n - 1);
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = Fac2(n);
printf("%d\n", ret);
}
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
//TDD --- 测试驱动开发 先想这个函数怎么用,再去具体实现这个函数
ret = Fib(n);
printf("ret = %d\n", ret);
return 0;
}
你会发现这个递归方式实现的函数在求n比较小的情况下是可以很快且正确的得到结果的,但是当n变大时,比如50,你会发现很久都不会出现结果!
50
49 48
48 47 47 46
47 46 46 45 46 45 45 44
你会发现在做很多重复的工作
所以这里使用递归的方式求解斐波那契数是不合适的。并不是只要使用递归就会变得简单。所以我们这里用循环的方式实现。
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1; // 这里在初始化的时候你可能会初始成0;但是当n等于1或者2的时候你进来不满足while循环直接返回c那就错了
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
int ret = 0;
scanf("%d", &n);
ret = Fib(n);
printf("ret = %d\n", ret);
return 0;
}
总结:但是当用递归的方式来解决问题时,并不是同时满足有限制且不断地接近目标值就是正确的,因为在这种情况下也有可能栈溢出和运行效率极低。
void test(int n)
{
if (n < 10000)
{
test(n + 1);
}
}
int main()
{
test(1);
return 0;
}
//栈溢出了,程序崩溃
提升:1.汉诺塔问题
2.青蛙跳台阶问题