C语言递归问题总结(汉诺塔问题、青蛙跳台阶、main函数的递归调用、斐波那契数列、阶乘、字符串逆序等)

一、函数递归

递归是什么

程序调用自身的编程技巧称为递归( recursion)。函数在自身的定义或者说明中直接或间接调用自身的一种方法,通常把大型复杂的问题转换成与原问题相似的小规模问题。

递归的条件

  • 存在限制条件,即递归有出口,满足条件时,递归不再继续。
  • 每次调用都会越来越接近这个限制条件。

用递归方法解决问题时,要以递归出口为突破点思考,将大问题转换成小问题,最终到递归出口,然后再逐层返回。

二、经典递归问题

1.汉诺塔问题

汉诺塔(Tower of Hanoi),又称河内塔,是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。——百度百科
对于汉诺塔问题,想要把n个圆盘从A移动到C,需要以下步骤:
1.把n-1个圆盘借助C移动到B
2.把剩下的一个圆盘移动到C
3.把B上的n-1个圆盘借助A移动到
在这里插入图片描述
程序:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<windows.h>

int count = 1;
//n表示圆盘数量,A B C为三个柱子
//A--->C表示将圆盘从A移动到C
void Hanoi(int n, char A, char B, char C)
{
    
    
	if (n == 1)
	{
    
    
		printf("第%10d步: %c--->%c\n", count++,A, C);
	}
	else
	{
    
    
		Hanoi(n - 1, A, C, B);//把A上的n-1个圆盘借助C移动到B
		printf("第%10d步: %c--->%c\n", count++, A, C); //把剩下的一个圆盘移动到C
		Hanoi(n - 1, B, A, C);//把B上的n - 1个圆盘借助A移动到c
	}
}

int main()
{
    
    
	//Tower of Hanoi
	Hanoi(64, 'A', 'B', 'C');
	system("pause");
	return 0;
}

2.青蛙跳台阶

一只青蛙一次可以跳上 1 级台阶,也可以跳上2 级。求该青蛙跳上一个n 级的台阶总共有多少种跳法。
思路: 设青蛙所处台阶为n(n>2,第一阶只有一种跳法,第二阶有两种),则分析可知,无论青蛙当前处于哪个位置,上一个位置都有两种可能,n-1或者n-2,所以当前位置的跳法为Jump(n)等于前两个位置的跳法之和,即Jump(n)=Jump(n-1)+Jump(n-2)。此问题的本质是斐波那契数列
在这里插入图片描述
程序:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<windows.h>

int JumpFloor(int floor)//floor表示台阶数
{
    
    
	if (floor == 1){
    
    
		//第一阶有一种跳法
		return 1;
	}
	else if (floor == 2){
    
    
		//第二阶有两种跳法
		return 2;
	}
	else
		//任意一阶的跳法是前两阶的跳法之和
		return JumpFloor(floor - 1) + JumpFloor(floor - 2);
}

int main()
{
    
    
	int floor = 10;
	printf("%d\n", JumpFloor(floor));
	system("pause");
	return 0;
}

**

3.main函数的递归调用

C语言比较宽松、灵活,允许main函数对自身的调用
如:求阶乘,不构造函数。
可以用主函数的递归来实现
程序:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<windows.h>
int n = 10;
int fac = 1;
void main()
{
    
    
	//求阶乘,不构造函数
	if (n==1)
	{
    
    
		fac = fac*1;
		printf("%d\n", fac);
		system("pause");
	}
	else
	{
    
    
		fac = fac*n--;
		 main();
	}
}

**

4.求阶乘

求n的阶乘。(不考虑溢出)
递归程序:

#include<stdio.h>
#include<windows.h>
#define NUM 5
int Factorial(int num)
{
    
    
	//1和0的阶乘为1
	if (num <= 1){
    
    
		return 1;
	}
	else
		return Factorial(num - 1)*num;
}

int main()
{
    
    
	//求n的阶乘。(不考虑溢出)
	printf("阶乘为:%d\n", Factorial(NUM));
	system("pause");
	return 0;
}

迭代程序:

#include<stdio.h>
#include<windows.h>
#define NUM 10
int Factorial(int num)
{
    
    
	int fact = 1;
	while (num)
	{
    
    
		fact *= num--;
	}
	return fact;
}

int main()
{
    
    
	//求n的阶乘。迭代
	printf("阶乘为:%d\n", Factorial(NUM));
	system("pause");
	return 0;
}

4.斐波那契数列

斐波那契数列(Fibonacci sequence),指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……
递归程序:

#include<stdio.h>
#include<Windows.h>
#define NUM 20

int Fib(int n)
{
    
    
	//前两项值为1,n小于2时直接返回1
	if (n <= 2){
    
    
		return 1;
	}
	else
	{
    
    
		//第n项的值为前两项之和
		return (Fib(n - 1) + Fib(n - 2));
	}
}

int main()
{
    
    
	printf("%d\n", Fib(NUM));
	system("pause");
	return 0;
}

递归方法求第n个斐波那契数,程序简洁,容易理解,但由于递归调用需要大量的形成和释放栈帧,所以效率低且容易发生栈溢出。
如图
在这里插入图片描述
在这里插入图片描述
从这个程序看可以看出,在计算Fib40时仅Fib3一项被重复计算的次数就已经达到几千万次,浪费了大量的内存空间。
迭代程序:

#include<stdio.h>
#include<Windows.h>
#define NUM 20
int Fib(int n)
{
    
    
	int first = 1;
	int second = 1;
	int third = 1;

	//n比2大几,循环执行几次,即进行几次加和求下一项
	//若n小于等于2,则循环不执行,直接返回third的值1
	while (n>2)
	{
    
    
		third = first + second;
		first = second;
		second = third;
		n--;
	}
	return third;
}

int main()
{
    
    
	printf("%d\n", Fib(NUM));
	system("pause");
	return 0;
}

5.打印整数的每一位

接受一个整型值(无符号),按照顺序打印它的每一位。 例如:
输入:1234,输出 1 2 3 4。
思路:
打印1234的每一位,可以先打印123的每一位,再打印1234%10即可;打印123的每一位,可以先打印12的每一位,再打印123%10;打印12的每一位,可以先打印1,再打印12%10;而对于一个一位数只需要直接打印即可,这里便是递归出口
递归程序:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<windows.h>
#define unsigned int u_int

void Print(u_int x)
{
    
    
	//若x>10,继续调用Print函数,直到x为一位数
	if (x /10){
    
    
		Print(x / 10);
	}
	//打印当前数的个位
	printf("%d ", x % 10);
}

int main()
{
    
    
	//接受一个整型值(无符号),按照顺序打印它的每一位。 \
	  例如: 输入:1234,输出 1 2 3
	u_int num = 0;
	printf("Please enter a number:");
	scanf("%d", &num);
	Print(num);
	system("pause");
	return 0;
}

本题要求按顺序打印,若改为逆序打印,只需要将打印当前数的个位的语句放到if判断之前即可实现。

6.strlen的模拟

递归程序:

#include<stdio.h>
#include<Windows.h>

int MyStrlen(char *pc)
{
    
    
	if (*pc){
    
    
		//当前指向不为0,则返回1+比当前小一个的字符串\
		  大事化小的过程
		return 1+MyStrlen(pc+1);
	}
	//当前指向不为0,则返回0
		return 0;
}

int main()
{
    
    
	//递归和非递归分别实现strlen,递归
	char arr[20] = {
    
     "hello world!" };
	printf("字符串的长度为:%d\n", MyStrlen(arr));
	system("pause");
	return 0;
}

迭代程序:

#include<stdio.h>
#include<Windows.h>

int MyStrlen(char *pc)
{
    
    
	int count = 0;
	while (*pc++)
	//*和++的优先级是相同的,运算符的结合都是自右向左的\
	  因此 ++只对pc产生作用,而不是*pc
	{
    
    
		count++;
	}
	return count;
}

int main()
{
    
    
	//递归和非递归分别实现strlen
	char arr[20] = {
    
     "hello world!" };
	printf("字符串的长度为:%d\n", MyStrlen(arr));
	system("pause");
	return 0;
}

7.字符串逆序

递归程序:

#include<stdio.h>
#include<Windows.h>
#include<string.h>
void Reverse(char*start,char*end)
{
    
    
	if (start < end){
    
    
		*start = *start^*end;
		*end = *start^*end;
		*start = *start^*end;
		Reverse(start + 1, end - 1);
	}
}

int main()
{
    
    
	//字符串逆置,递归
	char arr[] = {
    
     "Hello world!" };
	printf("%s\n", arr);
	int sz = strlen(arr);
	Reverse(arr, arr+sz-1);
	printf("%s\n", arr);
	system("pause");
	return 0;
}

迭代程序:

#include<stdio.h>
#include<Windows.h>

void Reverse(char *pc,int sz)
{
    
    
	char *start = pc;
	char *end = pc + sz - 1;
	while (start < end)
	{
    
    
		*start = *start^*end;
		*end = *start^*end;
		*start = *start^*end;
		start++;
		end--;
	}
}

int main()
{
    
    
	char arr[] = {
    
     "Hello world!" };
	printf("%s\n", arr);
	Reverse(arr, strlen(arr));
	printf("%s\n", arr);
	system("pause");
	return 0;
}

字符串保存在数组的内存图:
在这里插入图片描述
如图,在这个程序中用数组保存字符串时,若未定义数组大小,会默认在结尾加’\0’,所以用sizeof计算数组所占内存空间大小时,要考虑’\0’所占空间。而strlen只计算字符串长度,不计算’\0’。

8.计算一个数的每位之和

写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19
输入:1729,输出:19
**思路:**计算每一位之和,需要用到01模和除10,先加当前个位数的值,即x%10,再将x除以10并取模,加下一位的值直到只剩一位为止。相当于将计算一个数的每位之和的问题转换成计算当前个位数的值加上比其少一位的其他位数的值之和,这是将大问题转换成小问题的过程。
程序:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>

int DigitSum(int x)
{
    
    
	if (x / 10){
    
    
		return x % 10 + DigitSum(x / 10);
	}
	else
		return x;
}

int main()
{
    
    
	int num = 0;
	printf("Please enter a positive number:\n");
	scanf("%d", &num);
	printf("%d\n", DigitSum(num));
	system("pause");
	return 0;
}

9.递归实现n的k次方

编写一个函数实现n的k次方,使用递归实现
思路: 0的任何次方都是0,但是0的0次方无意义,任何数的0次方都是1。 一个数的几次幂,相当于他自己乘以自己几次,3次方就乘3次,k次方就乘k次。求n的k次方,可以求n乘以n的k-1次方,而n的k-1可以求n乘以n的k-2次方···,直到k为0。
程序:

#define  _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>

int Power(int n, int k)
{
    
    
	if (n == 0){
    
    
		return 0;
	}
	else if (k == 0){
    
    
		return 1;
	}
	else{
    
    
		return n*Power(n, k - 1);
	}
}

int main()
{
    
    
	//递归实现n的k次方
	int n = 2;
	int k = 10;
	printf("%d\n", Power(n, k));
	system("pause");
	return 0;
}

Guess you like

Origin blog.csdn.net/qq_44631587/article/details/120613607