最近接触的感觉比较有难度的算法题有一部分可以用递归来解决,今天来总结一下近期对递归的学习总结。
一,递归的存在意义
为何在我们需要递归?
通俗的讲,就是有些有些问题,看似很庞大,但其实可以将它分解为许多与原问题相似的小问题的总和,进而通过循环最终讲问题解决。
简而言之,递归就是
把大问题变成简易的小问题进行求解。
二,一个完整的递归需要哪些必不可少的条件呢
一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
在我看来,其中最关键的就是边界条件了,如何通过问题去寻找边界条件是解决的一个问题的关键。
三,递归在解决问题中的作用
1,利用递归解决一些简单重复问题,如求一个数的阶乘、菲波那切数列。
这个方面递归主要是进行简单重复,换言之,大部分这类问题可以同意可以用循环解决。
下面我以求一个数的阶乘为例:
用递归来做:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int jc(int n);//求阶乘的函数
int main(void)
{
int a;
scanf("%d",&a);
printf("%d",jc(a));
return 0;
}
int jc(int n)
{
if(n==0)
return 1;
else
return n*jc(n-1);
}
用循环来做:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(void)
{
int a,i,b;
b=1;
scanf("%d",&a);
for(i=1;i<=a;i++)
{
b*=i;
}
printf("%d",b);
return 0;
}
其他,类似于汉诺塔问题一类的题,用递归可以快速解出,但如果你对汉诺塔比较了解或者思考较多,就会发现其公式
n为圆盘个数,H(k)为n=k时移动盘子次数的最小值
H(k)=2^k-1
我们的递归不过是把公式从一个一个计算过去了,也算是简单的重复。
2,利用递归来遍历搜索。
这是我最近学习的重点,也是比较有感触的方面。
上周在codevs上刷题时遇到这样一道题:
已知 n 个整数 x1,x2,…,xn,以及一个整数 k(k<n)。从 n 个整数中任选 k 个整数相加,可分别得到一系列的和。例如当 n=4,k=3,4 个整数分别为 3,7,12,19 时,可得全部的组合与它们的和为:
3+7+12=22 3+7+19=29 7+12+19=38 3+12+19=34。
现在,要求你计算出和为素数共有多少种。
例如上例,只有一种的和为素数:3+7+19=29)
当时看到这道题我的思路是这样的:
先建立一个判读是否为素数的函数,若是素数,则返回1;否则返回 0
利用排列组合公式
将n中取k个数的所有可能数算出来,用malloc建立一个同样长度的整形数组,再将每一种可能均储存到这个数组中,在对数组遍历利用素数判断函数将素数数记录在count这个int类型变量中,最后输出即可。
然后我进行编码时发现很难用循环将所有的组合计算出来,而且就算计算出来了也不知道该将其存储在数组的哪一个位置,思考很久后查看了c的题解,当时脑子很糊涂,看了完整代码只知道利用了递归,但递归的核心思想很难理解,然后暂时就把这题放在了一边。
后来周内的分享会有同学讲到了这一题,我仔细的听了然后回去重新把题解看了一遍,终于把思路理清楚了。
题解的代码是这样的:
#include <stdio.h>
#include <stdlib.h>
int count,N,K,Array[20];
int IsPrime(int Num)
{
int i;
if(Num%2==0)
return 0;
for(i=3;i*i<=Num;i+=2)
if(Num%i==0)
return 0;
return 1;
}
void Input()
{
int i;
scanf("%d%d",&N,&K);
for(i=0;i<N;i++)
scanf("%d",&Array[i]);
}
void CreateCombination(int Left,int Right,int Sum)
{
int i;
if(Right==N)
{
if(IsPrime(Sum))
count++;
return ;
}
for(i=Left;i<=Right;i++)
CreateCombination(i+1,Right+1,Sum+Array[i]);
return ;
}
int main()
{
Input();
CreateCombination(0,N-K,0);
printf("%d\n",count);
return 0;
}
核心就是那个CreateCombination函数,它有三个int类型的传入值,这三个值分别有什么用呢?
第一个,left。
left是用来记录在数组的第left个开始进行选数,它保证了所有选过的数不会再被重复选中
这是非常重要的,如果你选的数被多次选中,要排除重复选中的组合的数目是非常困难的。
第二个,right
它有什么用呢?right是用来记录已经选中的数的数目
看这段代码的主函数 其中CreateCombination(0,N-K,0);
n-k保证了它选中k个数之后right会变成n也就是选了k个数,从二不再进入循环,而是进行素数的判断。
第三个,sum
这个很明显,sum是用来储存已经选中所有数的和。
最后的判断也是依靠sum来实现。
这样分析以后,我们的思路就很明了了。
**CreateCombination(0,N-K,0);**中每取一个数,b+1,b=N时进行判断并存入全局变量count中
那么,为什么for循环的条件是i<=left呢?
这是为了保证函数能在取了k-1个数后至少还有一个数剩余,使函数可以顺利运行。
通过这道题,我找到了在m个数中取n个数的代码实现这个框架,有许多题分析到底就是m个数中取n个数。
之后我做了一些类似题
1,设有一个长度为N的数字串,使用K个乘号将它分成K+1个部分,找出一种分法,使得这K+1个部分的乘积能够为最大。
代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int *p;
int n,k;
char q[41];
long long int max=0;
void input();
void one(int a,int b,long long int c);
int ten (int n);
int main(void)
{
input();
one(0,n-k-1,1);
printf("%d",max);
return 0;
}
void input()
{
int i;
scanf("%d %d",&n,&k);
p=(int*)malloc(n*sizeof(int));
scanf("%s",q);
for(i=0;i<n;i++)
{
p[i]=q[i]-48;
}
}
int ten(int n)
{
if(n==0)
return 1;
else
return 10*ten(n-1);
}
void one(int a,int b,long long int c)
{
int i,j;
long long int x;
x=0;
if(b==n-1)
{
for(i=a;i<n;i++)
{
x+=ten(n-1-i)*p[i];
}
if(c*x>max)
{
max=c*x;
return ;
}
return ;
}
for(i=a;i<=b;i++)
{
x=0;
for(j=a;j<=i;j++)
{
x+=p[j]*ten(i-j);
}
one(i+1,b+1,c*x);
}
return ;
}
这道题很明了,就是在n个数的n-1个空隙中选k个个插入乘号,和选数十分类似。
2,有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数)。
要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
代码:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int v,n,max;
int *p;
void one(int a,int b,int c);
void input();
int main(void)
{
input();
max=v-p[0];
one(v,0,0);
printf("%d",max);
return 0;
}
void input()
{
int i;
scanf("%d",&v);
scanf("%d",&n);
p=(int*)malloc(n*sizeof(int));
for(i=0;i<n;i++)
{
scanf("%d",&p[i]);
}
}
void one(int a,int b,int c)//a为容量,b表示上次取了数组中的第b-1个,c为累计加上的体积。
{
int i;
if(c<a)
{
if(a-c<max||max<0)
{
max=a-c;
}
}
if(c==a)
{
max=0;
return ;
}
if(c>a)
{
c=c-p[b-1];
if(a-c<max)
{
max=a-c;
return;
}
return;
}
for(i=b;c<a&&i<n;i++)
{
one(a,i+1,c+p[i]);
}
return ;
}
这道题虽然根本上不是从n个数中取m个,但十分类似,从n个数中取任意个,但终止条件不为取到几个,而是超过v,在将上次加上的体积减去再进行判断,较为复杂,但是是熟悉这种方法的好题。
四,结尾
最近关于递归的感悟大概就是这样,然后这是我第一次写博客,如果有缺点麻烦各位指出。