递归的理解与一些简单的例题

例题一:

输入n个字符进行全排列并将所有的可能存入二维数组中。

解题的思路是:

利用递归,我们现在假设对abcd进行全排列,那么我们递归下去对bcd进行全排列,然后递归下去对cd进行递归排列,然后继续递归到只有一个数字的时候,这时候这个数字的全排列就是数字本身,那么将其存入数组。递归此时应该回到上一层,此时有个关键的操作

      将每次递归时候本层扣留的那个字符和下面的每个字符交换然后继续递归就会得出最后的结果,如果不是很明白自己用张纸画一下就学会很明白的。但是每次交换之后记得换回来,不然顺序会混乱出现问题,那么这么就会出现一个问题,必须交换偶数次,如果先递归,然后交换,那么无法交换回来,会出现错误。这里就使用了一个小技巧,那就是先和本身交换一次(因为如果直接和下一个交换的话,那么每次本身结果就无法存入数组),然后递归再交换回来。

void swap(char *a,char *b)
{
   char temp;
   temp = (*a);
   (*a) = (*b);
   (*b) = temp;
}

void recursion(char *str,char **result,int start,int end,int *count,int length)
{
    if(start == end)
	{
	   for(int i = 0;i < length;i++)
		   result[(*count)][i] = str[i];
	   (*count)++;
	}
	else
	{
	   for(int i = start;i <= end ;i++)
	   {
		  swap(&str[start],&str[i]);
		  recursion(str,result,start+1,end,count,length);
		  swap(&str[start],&str[i]);
	   }
	}
}

例题二:回文的判断:

思路:

本题当然也可以不用递归来求解,也是非常的简单,但是为了训练我们的递归解题能力,我们现在来思考回文的递归的解法。

base case:aba,其中b就是一个基础情况,bb其中的最中心是没有的。那么就有两种情况是出口。然后我么只需要不断将大的字符串递归成小的字符串就行,每次递归比较首位,符合就递归下去,否则返回非。

bool isPa(char *str,int start,int length)
{
   if(length-1 <= start)
	 return true;
   else if(str[start] != str[length-1])
	 return false;
   else
	 return isPa(str,start+1,length-1);
}

例题三:

汉诺塔问题:

扫描二维码关注公众号,回复: 2260715 查看本文章

起初开始还没有注意这个问题,觉得非常简单,但是后面还是看了一下,突然感觉我对递归一下毛塞顿开,安利一下别人的一个故事,写得真的秒:

作者:Fireman A
链接:https://www.zhihu.com/question/24385418/answer/257751077
来源:知乎

对递归的理解的要点主要在于放弃!

放弃你对于理解和跟踪递归全程的企图,只理解递归两层之间的交接,以及递归终结的条件。

想象你来到某个热带丛林,意外发现了十层之高的汉诺塔。正当你苦苦思索如何搬动它时,林中出来一个土著,毛遂自荐要帮你搬塔。他名叫二傻,戴着一个草帽,草帽上有一个2字,号称会把一到二号盘搬到任意柱。

你灵机一动,问道:“你该不会有个兄弟叫三傻吧?”
“对对,老爷你咋知道的?他会搬一到三号盘。“
”那你去把他叫来,我不需要你了。“
于是三傻来了,他也带着个草帽,上面有个3字。

你说:”三傻,你帮我把头三个盘子移到c柱吧。“
三傻沉吟了一会,走进树林,你听见他大叫:”二傻,出来帮我把头两个盘子搬到C!“

由于天气炎热你开始打瞌睡。朦胧中你没看见二傻是怎么工作的,二傻干完以后,走入林中大叫一声:“老三,我干完了!”

三傻出来,把三号盘从A搬到B,然后又去叫二傻:“老二,帮我把头两个盘子搬回A!”

余下的我就不多说了,总之三傻其实只搬三号盘,其他叫二傻出来干。最后一步是三傻把三号盘搬到C,然后呼叫二傻来把头两个盘子搬回C

事情完了之后你把三傻叫来,对他说:“其实你不知道怎么具体一步一步把三个盘子搬到C,是吧?”

三傻不解地说:“我不是把任务干完了?”

你说:“可你其实叫你兄弟二傻干了大部分工作呀?”

三傻说:“我外包给他和你屁相干?”

你问到:“二傻是不是也外包给了谁?“

三傻笑了:“这跟我有屁相干?”

你苦苦思索了一夜,第二天,你走入林中大叫:“十傻,你在哪?”

一个头上带着10号草帽的人,十傻,应声而出:“老爷,你有什么事?”

“我要你帮把1到10号盘子搬到C柱“

“好的,老爷。“十傻转身就向林内走。

“慢着,你该不是回去叫你兄弟九傻吧“

“老爷你怎么知道的?“

“所以你使唤他把头九个盘子搬过来搬过去,你只要搬几次十号盘就好了,对吗?“

“对呀!“

“你知不知道他是怎么干的?“

“这和我有屁相干?“

你叹了一口气,决定放弃。十傻开始干活。树林里充满了此起彼伏的叫声:“九傻,来一下!“ “老八,到你了!““五傻!。。。“”三傻!。。。“”大傻!“

你注意到大傻从不叫人,但是大傻的工作也最简单,他只是把一号盘搬来搬去。

若干年后,工作结束了。十傻来到你面前。你问十傻:“是谁教给你们这么干活的?“

十傻说:“我爸爸。他给我留了这张纸条。”

他从口袋里掏出一张小纸条,上面写着:“照你帽子的号码搬盘子到目标柱。如果有盘子压住你,叫你上面一位哥哥把他搬走。如果有盘子占住你要去的柱子,叫你哥哥把它搬到不碍事的地方。等你的盘子搬到了目标,叫你哥哥把该压在你上面的盘子搬回到你上头。“

你不解地问:“那大傻没有哥哥怎么办?“

十傻笑了:“他只管一号盘,所以永远不会碰到那两个‘如果’,也没有盘子该压在一号上啊。”

但这时他忽然变了颜色,好像泄漏了巨大的机密。他惊慌地看了你一眼,飞快地逃入树林。

第二天,你到树林里去搜寻这十兄弟。他们已经不知去向。你找到了一个小屋,只容一个人居住,但是屋里有十顶草帽,写着一到十号的号码。

----------------------------------------------------------------------------------------------------------------------------------

以前理解递归的问题老是爱跟着一步步的走进去,那可是幂指数级别,难怪不得我们会感到困惑!其实现在看来这对理解递归反而并不是那么的号,索性就像数学归纳法一样(现在想来数学归纳法还真是厉害!)直接让n-1步变成整体,我们只先处理最后一步,出口,然后摸清递推关系就行了,中间怎么算的,管我们屁事!


我们在理解这个问题的时候,那么不管前面的n-1步,直接让前面n-1那一坨移动到B柱子上面去(因为我们不管哪个大傻子怎么移动的,反正这点肯定是可以办到的。)然后我们来干最轻松的活,那就是把最后一块移动到我们的目的地C柱上面去。ok,下面我们就让傻子把n-1块盘子搬到C柱子上面去,(因为我们不管哪个大傻子怎么移动的,反正这点肯定是可以办到的。因为把n-1移动到B柱子都是可行的,当然移动到C柱子也是可行的!)Perfect任务完成,但是你发现我们实际上只做了无数步中的一步,傻子(计算机)帮我们做完了大部分的活,是不是很开森!

void Han(int n,char from,char helper,char to)
{
   if(n == 1)
	 printf("plate From %c to %c\n",from,to);           //最后的出口
   else
   {
         Han(n-1,from,to,helper);                           //傻子干活了
	 printf("plate From %c to %c\n",from,to);           //我们的工作
	 Han(n-1,helper,from,to);                           //傻子干活了
   }
}

可能有的人会对第三步有疑惑,可能会想,为什么我不把接下来的n-1块搬回去,然后自然重复前面的动作。如果这样想的话,那么你就忽略了一个很重要的问题,那就是你在叫一个二愣子工作,他只会按照你的指挥做事,并不能拥有自动完成任务的能力,那么这样的话n-1个盘子会被般停在A柱子上。因为二愣子搬动那n-1个盘子的时候就是全部的递归了(你的递归的目的就是叫二傻子搬到那个柱子上面去,它完成任务就不会继续工作了,所以你要这样做的话那么你就要接着下命令才行!)!

例题四:

给定小括号的对数,输出所有组合!(用递归穷举的套路)

思路:利用递归来进行‘(’和‘)’的任意组合,有点类似二叉树的感觉。

void GetAll(int n,char * result,int left,int right,int count) {
	 if(left == 0 && right == 0)
	 {
	    for(int i = 0;i < count;i++)
		{
			printf("%c",result[i]);
		}
		printf("\n");
		return;
	 }
        if(left > 0)
	 {
	   result[count] = '(';
	   GetAll(n-1,result,left-1,right,count+1);
	 }
	 if(right > 0)
	 {
	   result[count] = ')';
	   GetAll(n-1,result,left,right-1,count+1);
	 } 
}

left,right是代表左右括号还没有参与的,所以很简单,还有左括号就叫二傻子加入左括号,还有右括号就叫二傻子加右括号。

注意这个count是用来加入最后结果的数组的个数的。

分享一个错误,我最开始把count在参数中式写成(*count)的,然后每次在存入数组的时候自己加,也就是会永久改变变量的数量,但是会在递归回溯的时候很出问题,需要深入到递归里面进行理解,写的条件比较复杂。所以递归还是那句话,放弃理解细节!

例题5:

You are climbing a stair case. It takes n steps to reach to the top.

Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

思路:该题如果采用递推穷举的方法是可以解出来的,那么采用的方法和上一题是类似的。

int climbStairs(int n)
{
        if(n == 1)
	  return 1;
	else if(n == 2)
	  return 2;
	else
	  return climbStairs(n-1) + climbStairs(n-2);          //大傻子和二愣子的活,我们只爬第一步和第二步!
}

猜你喜欢

转载自blog.csdn.net/c_living/article/details/79499657