最近关于递归的学习总结

最近接触的感觉比较有难度的算法题有一部分可以用递归来解决,今天来总结一下近期对递归的学习总结。

一,递归的存在意义

为何在我们需要递归?
通俗的讲,就是有些有些问题,看似很庞大,但其实可以将它分解为许多与原问题相似的小问题的总和,进而通过循环最终讲问题解决。
简而言之,递归就是
把大问题变成简易的小问题进行求解。

二,一个完整的递归需要哪些必不可少的条件呢

一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
在我看来,其中最关键的就是边界条件了,如何通过问题去寻找边界条件是解决的一个问题的关键。

三,递归在解决问题中的作用

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,在将上次加上的体积减去再进行判断,较为复杂,但是是熟悉这种方法的好题。

四,结尾

最近关于递归的感悟大概就是这样,然后这是我第一次写博客,如果有缺点麻烦各位指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值