算法一:递归(包含全排列)

递归

递归在算法中具有很重要的地位,也是很多学习编程的初学者非常头疼的问题,看我的这篇文章,希望能为还处于迷雾中的你带来希望

首先我们要知道递归的作用:

1.可替代多重循环

2.解决本来就是用递归形式定义的问题

3.将问题分解为规模更小的子问题进行求解

其实对于我来说,递归非常重要的原因在于可以替代多重循环,当循环过大时,会给计算机相当大的负荷,一时半会很难出结果,这时候我们就需要运用到递归这个手段。这里我会把知识由浅入深,以举例的方式让各位登堂入室,话不多说,直接上干货。

1.求阶乘

递归的基本概念就是一个函数调用其自身
我们都知道如何求阶乘,这是我们初中数学就学了的内容,那么如何用编程的思维解决这个问题呢
对于递归,我们首先要明确问题是什么,把抽象事物具体化
在这里插入图片描述
接下来我们需要厘清递归中的表达式或者关系
然后找到递归结束条件,也就是递归出口——不递归的条件。如果没有递归出口,那么函数只会像一个无底洞,结果大失所望
这是对应的C代码

#include<stdio.h>

int Factorial(int n)
{
	if(n == 0)
		return 1;
	else
		return n * Factorial(n - 1);
}

int main()
{
	printf("%d\n",Factorial(5));
	return 0;
}

其实递归很耗费内存资源的,因为每次调用一下自身就会需要创建一个新的栈来存放。
为了加深影响,我把程序改一下

#include<stdio.h>

int f(int n)
{
	if(n > 1)
		f(n - 1);
	printf("n  =  %d\n",n);
}

int main()
{
	f(5);
	printf("%d\n",f(5));		//printf的值是递归函数最后一行的双引号内的字符串的长度 
	return 0;
}

具体函数调用分析如下图
在这里插入图片描述
每次一个递归调用完后,所在的栈空间也会被立即释放,得到的结果会返回上一个栈,直到函数结束
既然基本套路熟悉了,我想带大家聊聊递归中很经典的问题,不用猜也知道,汉诺塔问题

2.Hanoi问题

在这里插入图片描述
这个问题为什么说是经典呢,因为它是个简单又很精髓的递归问题
通过该问题,你可以知道递归还有一个重要特点,就是把细节模块化,以整体的思想看待问题。

首先分情况讨论,
1.原目标塔上只有一个盘子,那么我们直接将盘子从A移到C即可
2.原目标塔上有不止一个盘子,又由于大盘在下小盘在下的规定,我们可以先将A座的盘子都移到B座以C为中间“变量”,当A座只剩最后一个盘子,也就是全场最大的盘子,将其移到C座,然后以A为中间“变量”,将B座盘子移到C座

具体C代码如下

#include<stdio.h>

void Hanoi(int n, char src, char mid, char dest)
{
	if(n == 1)
	{
		printf("%c -> %c \n", src, dest);
	}
	else
	{
		Hanoi(n - 1, src, dest, mid);
//		printf("%c -> %c \n", src, dest);
		Hanoi(1, src, mid, dest);
		Hanoi(n - 1, mid, src, dest);
	}
		
}

int main()
{
	int n;
	printf("Please enter number : ");
	scanf("%d",&n);
	Hanoi(n, 'A', 'B', 'C');
	return 0;
}

对于递归,记住不要纠结具体细节,把握整体框架最为重要!

这里还有一个小练习,关于求最大公约数的问题,比方说求12和8的最大公约数,你可以先想想,然后再看我的代码答案,记得,要用递归做哦!

#include<stdio.h>

int gcd(int a, int b)
{
	if(b == 0)
		return a;
	else
		return gcd(b, a % b);
}

int main()
{
	printf("%d\n",gcd(12,8));
}

是不是很简单,递归有的时候就有点像函数调用,它只是比较特殊,它只是总是在调用自己罢了。如果除数等于零,那么被除数就是最大公约数,如果除数不等于零,那么就把b的值给a,b的值变为a % b。递归还有一个特点,就是以十分简易的代码实现了值的互换或者修改

3.N皇后问题

N皇后问题是八皇后问题的延伸,要求在NxN格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

扫描二维码关注公众号,回复: 9852823 查看本文章
#include<stdio.h>
#include<math.h>

int queenPos[100]; 
int N;

void NQueen(int k)		//在0~k-1行皇后摆好的情况下摆第k行的皇后 
{
	int i;
	if(k == N)
	{
		for(i = 0; i < N; i++)
			printf("%d ",queenPos[i] + 1);
		printf("\n");
	}
	else
	{
		for(i = 0; i < N; i++)		//逐步尝试第k个皇后的列位置 
		{
			int j;
			for(j = 0; j < k; j++)
			{
				if(queenPos[j] == i || abs(k - j) == abs(queenPos[j] - i))
					break;
			}
			if(j == k)
			{
				queenPos[k] = i;
				NQueen(k + 1);
			}
		}
	}
} 

int main()
{
	scanf("%d",&N);
	NQueen(0);
	return 0;	
} 

这里我们不妨设最多有100个皇后,定义数组queenPos[100],然后再设全局变量N用于记录有多少皇后,NQueen(int k)是用于记录在0~k-1行皇后摆好的情况下摆第k行的皇后。如果全都摆好了,即k == N,那么把可能摆放位置的其中一种输出,如果栈都退完了那么就结束了,否则销毁当前这个栈再往回退栈。如果k != N,那么先循环遍历N个可能的位置给第k行的皇后,然后再把暂时确定的第k行皇后的位置与前k-1行所有皇后比较,如果有产生冲突的则再改变第k行皇后的位置。如果j==k,那么就确定第k行皇后的位置,同时进入下一层NQueen(k+1)。
这就是这个递归的思路
四皇后是N皇后最小的底线,大家可以试一试,答案是在这里插入图片描述

4.逆波兰表达式

逆波兰表达式的定义:
1)一个数是一个逆波兰表达式,值为该数;
2)“运算符 逆波兰表达式 逆波兰表达式”是逆波兰表达式,值为两个逆波兰表达式的值运算结果。

这里我不妨设整个表达式所占的大小不超过20个字符,代码如下:

#include<stdio.h>
#include<stdlib.h>

double exp()
{
	char s[20];
	scanf("%s",s);
	switch(s[0])
	{
		case '+' :
			return exp() + exp();
		case '-' :
			return exp() - exp();
		case '*' : 
			return exp() * exp();
		case '/' : 
			return exp() / exp();
		default : return atof(s);
		break;
	}
}

int main()
{
	printf("%lf",exp());
	return 0;
}

atof()函数是stdlib.h中的函数,将字符串内容转化成double类型。
当输入* + 1 2 - 8 6
结果为在这里插入图片描述

5.爬楼梯

LiHua爬楼梯,每次可以走1级或者两级,要求输入楼梯级数,求不同的走法数

这里我们可以吧问题拆分细小化

n级台阶的走法 = n - 1 级台阶走法 + n - 2 级台阶走法

例如共有5级台阶,可拆分成第一步走一级和第一步走两级的走法,然后再从第一步走一级再细分之后的台阶走法。
在这里插入图片描述
C代码如下:

#include<stdio.h>

int N;

int stairs(int n)
{
	if(n < 0)
		return 0;
	if(n == 0)
		return 1;
	if(n == 1)
		return 1;
	if(n == 2)
		return 2;
	return stairs(n - 1) + stairs(n - 2); 
}

int main()
{
	scanf("%d", &N);
	printf("%d\n", stairs(N));
	return 0;
}

这里还有一个类似爬楼梯的题目,可以参照这篇博客2020 计蒜客蓝桥杯省赛 B 组模拟赛(一)题解2.爬楼梯

6.放苹果

在这里插入图片描述
关于递归最重要的无非是递归表达式和递归终止条件。
首先分析,需要输入苹果数和盘子数。关于两者的数目就存在两种情况:
1、苹果数n大于盘子数m,n > m时,f(n,m) = f(m,m);
2、苹果数n小于等于盘子数m,n <= m时,f(n,m) = f(n, m - 1) + f(n - m,m),即有盘子为空的放法 + 无盘子为空放法。

详细代码如下:

#include<stdio.h>

int f(int n, int m)
{
	if(n < m)
		return f(n, n);
	if(n == 0)
		return 0;
	if(m == 0)
		return 1;
	else
		return f(n, m - 1) + f(n - m, m);
}

int main()
{
	int t, n, m, i;
	scanf("%d", &t);
	for(i = 0; i < t; i++)
	{
		scanf("%d %d", &n, &m);
		printf("%d\n", f(n, m));
	}
	return 0;
}

三个苹果三个盘子就是4
在这里插入图片描述
经过这么多练习可以发现,递归很多时候都用于直接就出结果,不在意输出过程的题目。

7.全排列

全排列是递归中非常重要的知识点,在深度学习中的用处很广。比方说我对1、2、3、4、5进行全排列,那么如下图所示
在这里插入图片描述
首先让第一个位置有n种摆法,然后就只用考虑p+1到q的全排列了,把一个大的全排列分解为一层层嵌套的子问题这便是递归思想,注意最后还要把交换过的元素换回来,要不然会使一些情况重复。
详细代码如下

#include<stdio.h>

void swap(int A[], int i, int j)
{
	int temp = A[i];
	A[i] = A[j];
	A[j] = temp;
} 

void printArray(int A[], int n)
{
	int i;
	for(i = 0; i < n; i++)
	{
		printf("%d ", A[i]);
	}
	printf("\n");
}

void perm(int A[], int p, int q)
{
	if(p == q)
		printArray(A, q + 1);
	else
	{
		int i;
		for(i = p; i <= q; i++)
		{
			swap(A, p, i);
			perm(A, p + 1, q);
			swap(A, p, i);
		}
	}
}

int main()
{
	int A[5] = {1, 2, 3, 4, 5};
	perm(A, 0, 4);
}

这里还有一个类似的全排列题型,可以参看博文蓝桥杯2015第六届C语言B组省赛习题题解——习题E.九数组分数

这里我列出九数组分数的代码,你们可以观察一下,思路其实都是一样的,先swap然后递归再swap换回来。
在这里插入图片描述

#include <stdio.h>

void test(int x[])
{
	int a = x[0]*1000 + x[1]*100 + x[2]*10 + x[3];
	int b = x[4]*10000 + x[5]*1000 + x[6]*100 + x[7]*10 + x[8];
	
	if(a*3==b) printf("%d / %d\n", a, b);
}

void f(int x[], int k)
{
	int i,t;
	if(k>=9){
		test(x);
		return;
	}
	
	for(i=k; i<9; i++){
		{t=x[k]; x[k]=x[i]; x[i]=t;}
		f(x,k+1);
		t=x[k]; x[k]=x[i]; x[i]=t; // 填空处
	}
}
	
int main()
{
	int x[] = {1,2,3,4,5,6,7,8,9};
	f(x,0);	
	return 0;
}

如果喜欢我的文章,请记得三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持,下期更精彩!!!

发布了40 篇原创文章 · 获赞 7 · 访问量 3107

猜你喜欢

转载自blog.csdn.net/qq_44631615/article/details/104558299