函数递归--(汉诺塔问题)

一 ,什么是递归?

 写代码是为了解决问题,而递归是可以帮助我们解决问题的方法!在C语言中,而递归就是自己调用自己。如果又一个大型且很复杂的问题,我们可以层层把它拆解成一个与原问题相似,但规模小的子问题了求解;直到子问题不可被拆分,递归就结束了,所以,递归的思考方式就是把大事化小的过程!大事化小

递归中的 ---------->递推

               归 ---------->回归

例题1:

------->思考输入2时,打印出来的数字是多少?

#include <stdio.h>
int Fun(int n)
{
	if (n == 5)
		return 2;
	else
		return 2 * Fun(n + 1);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int c = Fun(n);
	printf("%d\n", c);
	return 0;
}

在思考的过程中,其实脑袋里的过程,是不断往“细化”中走

 

 例题2:

写一个史上为简单的C语言递归代码:

int main()
{
	printf("hehe\n");
	main(); //在main函数中又调用了main函数
	return 0;
}
上述就是⼀个简单的递归程序,只不过上面的递归只是为了演示递归的基本形式,不是为了解决问
题,代码最终也会陷入死递归,导致栈溢出(Stack overflow==>无数次的向内存申请空间,最终内存的空间被耗干。

二 ,递归的限制条件?

递归在书写的时候,有两个必要条件:

1.递归存在限制条件,当满足这个限制条件的时候,递归便不再继续

2.每次递归调用之后,会越来越接近这个限制条件。 

(简而言之,就是递归得有限制条件,每次的调用,接近这个限制条件)

三 ,递归案例

案例1: 

 求n的阶乘:

一个正整数的 阶乘(factorical) 是所有小于及等于该数的正整数的积,并且0的阶乘为1自然数 n 的阶乘写作 n!

这里不考虑n太大的情况,n太大会存在溢出!

题目:计算n的阶乘(不考虑溢出),n的阶乘就是1~n的数字累积相乘 

 n 的阶乘的公式 :n! = n*(n-1)!

举例:

                           5! = 5 * 4 * 3 * 2 * 1

                           4!    =4! * 3 * 2 * 1

=========== >5!    = 5 * 4!

思路: 

这样的思路就是把一个较大的问题,转换为一个与原问题相似,但规模较小的问题来解决! 

用图来表示:

 

 代码实现:
// n 的阶乘
#include <stdio.h>

int Fact(int n)
{
	if (n == 0)
		return 1;
	else
		return n * Fact(n - 1);
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int c = Fact(n);
	printf("%d\n", c);
	return 0;
}

注:为什么n的值太大会导致栈溢出?

==== > 在C语言中,每一次函数调用,都要为这次函数调用在栈区申请一块内存空间,用来为这次函数调用存放信息,这一块空间就叫:运行时堆栈  或者  函数栈帧空间  ,如果函数不返回,函数对应的栈帧空间就一直占用,所以如果函数调用中存在递归调用的话,每一次递归函数调用都会开辟属于自己的栈帧空间,知道函数递归不再继续,开始回归,才逐渐释放栈帧空间。

所以如果采用函数递归的方式完成代码,递归层次太深,就会浪费太多的栈帧空间,也可能引起栈溢出(stack overflow); 

 案例2:

题目:顺序打印一个整数的每一位(输入一个整数 m,按照顺序打印整数的每一位)

比如:

输入:1234         输出:1  2  3  4

输入:520           输出:5  2   0 

 思路:

1. 首先是:如何得到数字的每一位数字呢?

但是这里有一个问题,就是我们得到的数是倒着的

2. 但是我们有了灵感,我们发现其实⼀个数字的最低位是最容易得到的,通过%10就能得到

那我们假设想写⼀个函数Print来打印n的每⼀位:

 

代码实现:
#include <stdio.h>
void Print(int n)
{
	if (n > 9)
		Print(n / 10);

	printf("%d ", n % 10);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	Print(n);
	return 0;
}

四 ,递归与迭代

递归是⼀种很好的编程技巧,但是和很多技巧⼀样,也是可能被误用的,就像案例1⼀样,看到推导的公式,很容易就被写成递归的形式:所以如果不想使⽤递归,就得想其他的办法,通常就是迭代的方式(通常就是循环的方式)。

以下是案例1的非递归方式求解:

int main()
{
	int n = 0;
	scanf("%d", &n);
	int fact = 1;
	for (int i = 1; i <= n; i++)
	{
		fact *= i;
	}
	printf("%d\n", fact);
	return 0;
}

上述代码是能够完成任务,并且效率是比递归的方式更高

事实上,我们看到的许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更加清晰,但是这些问题的迭代实现往往比递归实现效率更高。当⼀个问题非常复杂,难以使用迭代方式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销

案例3:

题目:求第 n 个数的斐波那契数

计算第n个斐波那契数,是不适合使用递归求解的但是斐波那契数的问题通过是使用递归的形式描述的

代码实现+分析:
1.递归方式:
int Fib(int n)
{
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int r = Fib(n);
	printf("%d\n", r);
	return 0;
}

 输出:

但是当我们输入求第55个斐波那契数时:

代码一直在疯狂计算! 

当我们n输⼊为50的时候,需要很长时间才能算出结果,这个计算所花费的时间,是我们很难接受的,这也说明递归的写法是非常低效的!

我们可以发现在不断往下递归的时候,数字会 不断的被重复运算,而且这个重复是指数级的增长!

我们可以试着算一下3这个数字,在求斐波那契数的时候,被求了多少次:

int count = 0;

int Fib(int n)
{
	if (n == 3)
		count++;

	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int r = Fib(n);
	printf("%d\n", r);
	printf("count = %d\n", count);
	return 0;
}

这⾥我们看到了,在计算第40个斐波那契数的时候,使⽤递归⽅式,第3个斐波那契数就被重复计算了39088169次,这些计算是非常冗余的。所以斐波那契数的计算,使用递归是非常不明智的,我们就得想迭代的方式解决
int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 0;
	if (n <= 2)
		return 1;
	while (n > 2)
	{
		c = a + b;
		a = b;
		b = c;
		n--;
	}
	return c;

}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int r = Fib(n);
	printf("%d\n", r);
	return 0;
}

 有时候,递归虽然好,但是也会引入一些新的问题,所以我们一定不要迷恋递归,适可而止就好!

五 ,汉诺塔问题:

汉诺塔是什么?

汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘

我们思考一下,如果要完成64个盘子的转移,需要花多长时间呢?

假如可以每秒钟移动一个盘子,我们以一年366天这么算,一共需要

583,344,214,028.96527825212507589557年  ===>5833.45亿年

思路:

1.如果我们只有一个圆盘的话,我们可以直接A->C

2.如果有两个的话,A->B  A->C  B ->C

3.如果有三个的话   A -> C   A -> B   C -> B   A -> C   B -> A   B -> C   A -> C

以此类推:

发现每次完成的次数是 2^n -1

代码实现:

void move(char pos1, char pos2)
{
	printf(" %c -> %c  ", pos1, pos2);
}

// n :盘子个数
//pos1:起始位置
//pos2:中转位置
//pos3:目的位置

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()
{
	Hanoi(1, 'A', 'B', 'C');
	printf("\n");
	Hanoi(2, 'A', 'B', 'C');
	printf("\n");
	Hanoi(3, 'A', 'B', 'C');
	printf("\n");
	Hanoi(4, 'A', 'B', 'C');
	printf("\n");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值