递归

通篇隐喻,看官自明。

                                                                                       《目录》

  • 递推的感性认识
  • 递归的感性认识
  • 八皇后摆放问题
  • 蹭同桌饭的游戏
  • 为你写诗 --递归
  • 练习·为你(递归)写诗
  • 尾递归与不爆栈的递归

 开元盛世

        开元二十三年(735年)的长安,唐玄宗亲临五凤楼,恩赐百姓宴饮狂欢,还让三百里之内的地方官带歌舞团进京,在楼前表演竞技。

        行路难,归去难!

        就在这一年,二十四岁的杜甫考场失利。身上的盘缠抵不上长安的物价,也没有亲友可以长久依靠。于是,如同现在去一线城市打拼却拼不出一丝光明的年轻人一样。把身上仅有的盘缠去旅行......

        走到山东时,看见高耸的泰山。那雄浑的景色让ta心旌摇荡 (面对自己想要或想得到的东西,心驰神往,不能自制),还处在山脚时便被山势激起豪情而写下了《望岳》。

        会当凌绝顶,一览众山小。

与生活中平常视角极其不同的是:登上泰山,眼前的一切都便的微不足道了。不仅激发杜甫的热血,还有、还有ta告诉我们,种种碎屑的苦闷只是因为站的太低、看得太近...

那么,有没有打开眼界的方法呢 ... ...


人,人天然的思维方式叫正向思维,而人的正向思维被称为 "递推"(Iterative)。递推是人本能的正向思维,我们小时候学习数数,从1、2、3 ··· 100 ··· 10000 ··· ∞,就是典型的递推;这种学习过程是循序渐进,由易到难,由小到大,由局部到整体,出发点是正向的;

其实人对数字没什么概念,数字多寡只是一个名称,对大数基本是傻的,甚至对小的数也不敏感,习惯从小到大渐渐扩展的思维。

山水之乐,得之心而寓之酒也。

小学时,学习解方程,学习解一元方程(一个未知数x);

中学时,学习解方程,学习解二元(俩个未知数x,y)、三元方程(三个未知数x,y,z);

大学时,学习解方程,学习解任意未知数方程(线性方程组)、齐次方程、微分方程;

类似于,学习语言时,先从拼音开始,再组词,后简单造句,接着使用"因为...所以..."、"既然...于是..."、"不但...而且" 等连接词,造成稍稍复杂的句子,这种思维方式便是递推,ta是正向的。

如果用递推的方式计算一个整数的阶乘, 7! = 1 * 2 * 3 * 4 * 5 * 6 * 7。在生活里,我们并不觉得ta不合理因为这是天然而成的;

中学的数学归纳法就是递推方式的典型应用,在高中的数列里面是最常用。

资料:数学归纳法


但世界上所有问题,不是一种角度就可以解决,例如,19世纪国际象棋棋手贝瑟尔提出一个问题,在 8 * 8 格但国际象棋上摆放 8 个皇后,使其不会相互攻击(任意俩个皇后不能在同一列、行、斜线),问有多少种摆放 ?

递推的思路 : 首先在第一行摆好一个棋子,它当然不会和任何棋子有冲突,然后再在第二行摆一个棋子,保证它和第一个棋子没有冲突,然后再摆第三行的,以此类推。下边给出了一种可能的解法。

递归_第1张图片

数学王子---高斯穷其一生只找到了76种方案(其他人是20多种)。当然今天用计算机很快就能找到它的全部92种结果。如果您花几分钟,一定可以找到一种,而找到 ta 所有可能的摆法近乎不可能,我把原因告诉您。

人之所以做不到穷举所有合理的摆放方法,倒不完全是因为它的排列组合非常多。事实上,“八皇后”问题所有可能的组合只有4万种,并不算多,更何况在这4万种排列中,大量的情况是一眼看上去就知道不合理的,可以马上排除。因此,即使一个智力中庸的人手工处理4万种情况,照说有几个月也该搞清楚了,更何况是数学天才高斯。高斯没有做出来并不是因为他水平不够,实际上当时很多的国际象棋选手们都在解这道题,找到的答案只有20多种,还不如高斯呢。这里面主要的原因是人类固有的递推思维方式,也就是从前到后,从小到大,并不适合解决这道题。

那么问题该怎么解决?也许有 76 + 1 种呢 ?

数学家推算 8皇后问题解法共 92 种。以 12 种 8皇后 排列方法延伸(人要推出12种并不难),把11种中每一种每逆时针旋转 90 度,即可得到一种排列方法,旋转一周即可得到 4 种排列。得到这 4 种排列又按照右下左上的对角线对称翻身,又可得到 4 种方法,所以这里一共 11 * (4+4) = 88 种,而第十二个排列TA只能旋转90度2次,所以也只能翻身2次。算出来就是 88 + (2+2) = 92 种。

递归_第2张图片

           p.s. 这是一种解法,把这张图旋转90度,又可以得到另一种解法!


计算机,计算机的正向思维被称为 "递归"(recursive),计算机天生就是为大数处理而制造的,习惯从大到小、层层分解的思维

计算机是怎么计算阶乘的呢?它是倒着来的。比如要算5!,计算机就把它变成5x4!(五乘以四的阶乘)。当然,你会说,4!还不知道呢。没关系,计算机会说,采用同样的方法,把它变成4x3!。至于3!,则用同样的算法处理。最后做到1!时,计算机知道了它就等于自己,即1!=1,从此不再往下扩展了。接下来,就是倒推回所有的结果,由于知道了1!,2!,然后3!,4!,5!就统统都知道了,ta只需要依靠一个栈的数据结构(先进后出,后进先出,艾伦·图灵1946年提出,解决子程序的调用和返回)。从上向下展开 5, 4, 3, 2, 1 得到一个具体的结果就从下往上 1,2,3,4,5,当栈里面的数字全部出栈,递归算法就结束了。

这样的算法逻辑非常简单。递归过程的每一步用的都是同一个算法,计算机只需要自顶向下不断重复而已。具体到阶乘的计算,无非就是某个数字N的阶乘,变成这个数乘以N-1的阶乘。因此,递归的本质有两条:其一是自顶而下,其二是自己不断重复

用递归的方式解决八皇后问题,思路十分清晰:

【宏观】

1. 假如棋盘上已经有了 7 个皇后,它们彼此的位置不冲突,那么在第八行从第 1 个位置到第 8 个位置一个一个地试验即可。当然,你会问前七个皇后怎么摆,才能保证它们彼此不冲突呢?答案很简单,按照第八步的方法照猫画虎地做。


2. 当棋盘上一个棋子没有的时候,从第一行的第一个位置到第八个位置一个个分别试一试。只关心自己的下一层,而不是更下面的细节。

【微观】

1. 采用回溯法(backtracking)

2. 编写一个函数,把一个皇后放在某行的第 1 列,检查TA是否与棋盘上的其TA皇后相互攻击的局面,函数返回一个状态。

3. 如果皇后可放在这个位置,函数便调用自身,把皇后放在下一行。

4. 当递归调用返回时,函数再把原先那个皇后移到下一列。

5. 最后一个皇后成功置于最后一行时,函数打印棋盘,并显示 n 个皇后的位置

有的摆放往后可以走得通,有的走不通。如果走不通,计算机会直接跳到下一个位置去试验。实际上在4万个摆法中,有92个是能走通的。4 万个摆法对计算机连 1 秒都不需要。

递归_第3张图片

#include 
#include   // 非标准头文件, 调用getch( ) 可以一起去掉,功能 : 按任意键继续的
#define Abs(x) ((x^(x>>31)) - (x>>31)) // 取绝对值

int iCount = 0; // 记录解的序号的全局变量
int Queens[8]; // 记录皇后在各列上的放置位置的全局数组


void Output( ) // 输出一个解,即一种没有冲突的放置方案
{
   int i, j, flag = 1;
   printf("第%2d 种方案(♥表示皇后):\n", ++iCount); // 输出序号。 
   printf("  ");
   for( i = 1; i <= 8;i ++ )
       printf("▁");
   putchar(10);
   for ( i = 0; i < 8; i ++ )
   {
       printf("▕");
       for ( j = 0; j < 8; j ++ )
       {
           if( Queens[i]-1 == j )
               printf("♥");
           else
           {
               if ( flag < 0 )
                   printf("  ");
               else
                   printf("█");               
           }
           flag = -1 * flag;
       }
       printf("▏\n");
       flag = -1 * flag;
   }
   printf("  ");
   for( i = 1; i <= 8; i ++ )
       printf("▔");
   putchar(10); 
   getch();   // conio.h 调用getch( ) 可以一起去掉
}

int IsValid( int n ) // 判断第 n 个皇后是否与前面皇后行成攻击
{
   int i;
   for ( i = 0; i < n; i ++ ) // 第 n 个皇后与前面 n-1 个皇后比较 
   {
 	  if ( Queens[i] == Queens[n] ) // 两个皇后在同一行上,返回 0
		 return 0;
 	  if ( Abs(Queens[i] - Queens[n]) == (n - i) ) // 两个皇后在同一对角线上,返回 0
		 return 0;
   }
   return 1; // 没有冲突,返回 1
}

void Queen( int n ) // 在第 n 列放置皇后
{
   int i;
   if ( n == 8 ) // 若 8 个皇后已放置完成
   {
	  Output();  // 输出 
	  return;
   }
   for ( i = 1; i <= 8; i ++ ) // 在第 n 列的各个行上依次试探
   { 
	  Queens[n] = i; // 在该列的第 i 行上放置皇后 
	  if ( IsValid(n) ) // 没有冲突,就开始下一列的试探
		 Queen(n + 1);  // 递归调用进行下一步 
   }
}
int main( void )
{ 
   printf("八皇后排列方案:\n"); 
   Queen(0); // 从第0列开始递归试探 
   getch();  // 可以换成 system("pause")
   return 0;
}

对于“八皇后”这种问题,只能在逻辑上和实现上,都采用递归的方法,很难采用递推,即使采用了递推,也不过是人工实现递归中的堆栈操作。


拆解问题的过程就是一个自顶向下的过程。


e.g. 上海有多少辆自行车这个问题,先拆解成自行车数量 = 个人自行车数量 + 公用自行车数量 + 共享单车数量。
       个人自行车数量 = 人均个人自行车数量 * 总人数
       公用自行车数量 = 公用自行车点的数量 * 每个点的自行车数量。
       共享单车数量 = 共享单车平均密度 * 面积。
这种拆解方法就是从顶层开始拆解,这或许也是为什么谷歌等公司愿意将这类问题当面试题的原因。

计算机是人制造出来处理大场景的,对于大小场景也可以使用递推、递归切换使用。

递推:  一直不相信明天不能到来,所以,纵情做自己喜欢的事情,整个人生不会会坚定的目标,整个人生没有一件坚持到底的事情。去世时刻,回望一世倒是匆匆、静静。

递归:  如果把做事情这种倒推的方式扩展到极限,那就是按照生命的长度倒推自己这辈子该做的事情。懂得取舍,抓住主要因素。时时刻刻,纵心而为,这一刻恰恰迎来,静静的享受这一刻是多么满足和充实。苍老而疲惫的身体掩不住,明亮的眸子戳过恍惚迷茫的时间海洋,许多人在和我们 ??‍♂️ 挥手告别。


递归的食用指南

递归的原理在计算机科学中更多地体现在逻辑上的做事方法,在这方面它没有太多的缺点。

在具体实现上,如果直接采用递归的方法,逻辑简单,代码很短,非常漂亮。但是,由于要不断地把中间状态放到堆栈中(成为压栈或者入栈),然后再出来(成为弹出栈或者出栈),占用空间很多。尾递归如果在得到编译器的帮助下,是完全可以避免爆栈的原因:每一个函数在调用下一个函数之前,都能做到先把当前自己占用的栈给先释放了,尾递归的调用链上可以做到只有一个函数在使用栈,因此可以无限地调用!这并不是所有语言都摆支持,有些语言,比如说 python, 尾递归的写法在 python 上就没有任何作用,该爆的时候还是会爆,C语言是支持的 ~


因此,在具体实现时,很多情况是使用递归的原则设计,递推的原则实现。比如真正做阶乘运算,并不需要从大往小计算,还是可以从小往大算的。思路相同,只是实现方案不一样。

另外,给出 Python 版本的八皇后

abs = lambda abs:-abs if abs < 0 else abs   #  取绝对值函数

Queens=[0,0,0,0,0,0,0,0] # 记录皇后在各列上的放置位置的全局列表
iCount = 1               # 记录解的序号的全局变量

def Output( ):           # 输出一个解,即一种没有冲突的放置方案
	global iCount
	flag = 1
	print("第%2d 种方案(♥表示皇后): " % iCount)
	iCount += 1
	print(" ",end='')
	for i in range(8):
		print("▁",end='')
	print(' ')
	for i in range(8):
		print("▕",end="")
		for j in range(8):
			if Queens[i]-1 == j:
				print("♥",end="")
			else:
				if flag < 0 :
					print("  ",end="")
				else:
					print("█",end="")           
			flag = -1 * flag
		print("▏")
		flag = -1 * flag
	print("  ",end="")
	for i in range(8):
		print("▔",end="")
	print("")
   
def IsValid( n ):            # 判断第 n 个皇后是否与前面皇后行成攻击
	global Queens
	for i in range(n):   # 第 n 个皇后与前面 n-1 个皇后比较
 	  if ( Queens[i] == Queens[n] ):                # 两个皇后在同一行上,返回 0
 	  	return 0;
 	  if (abs(Queens[i] - Queens[n]) == (n - i)):   # 两个皇后在同一对角线上,返回 0
 	  	return 0
	return 1             # 没有冲突,返回 1
   
def Queen( n ):              # 在第n列放置皇后
	global Queens
	if  n == 8 :         # 若 8 个皇后已放置完成
		Output( )    # 输出
		return
	for i in range(1,9): # 在第 n 列的各个行上依次试探
		Queens[n] = i  # 在该列的第 i 行上放置皇后 
		if IsValid(n):   # 没有冲突,就开始下一列的试探
			Queen(n + 1)  # 递归调用进行下一步 

if __name__ == '__main__':
	print("八皇后排列方案:\n")
	Queen(0);             #  从第0列开始递归试探 

看电视剧有感:

        坏人的智商比坏人自己生活的圈子是高的、做事果断,失败是因为有圈子外面的人来了,圈子外面的人也许更理性、更热情;

        很少人天然就坏,但生活仿佛安排好了,"猪脚"自己又选择贪婪(局部最优),于是递推的走向了以前的反边。也许,要舍得才会跳出困境的极限递归的走才能完成大事。 我们要比坏人强。至于怎么比他们强,不完全是比他们更努力,因为这只是在低层次上的竞争,而是首先在见识和格局上要比他们高,气度要更大,这句话就是告诉您多采用采用递归的思维方式。

行万里路和读万卷书是相辅相成的,千万不要只做其一。

平常生活中,行万里路可以把您摆脱工具意识

因为在熟悉的场景里,您看到的一切或多或少都会工具化。

  • 当我站在十字路口,随便看看或者哪怕不看,我都知道这一条路是通往公司的,要在什么时间坐车才能避开早晚高峰;
  • 另一条路是美食街,下班的时候可以来这里放松一下,填饱肚子。
  • 我还知道哪一栋楼是医院,挂号要从哪个门进,哪一路公交车能把我载到哪个朋友的家……一切对我来说都是达成某个具体目标的工具。

一个人如果常年生活在工具化的心态里,一定会面目可憎、一脸俗气,连他自己都觉得乏味。这时候,就有必要换到一个陌生的场景。工具化心态也会让人低迷,感到对人生的无望......

当我站在新的十字路口上,我并不知道每一条路通往何方,也并不急着赶路。这就意味着,这个路口对你来说并不是通往某个目标的工具。

这样的话,就算走错了路我也不会生气,只会用悠然的心态欣赏街景。步子快一点或慢一点都无妨,因为没有了目标,就不需要执行力;而不需要执行力,当然就不需要苛刻的时间表。

今天的很多人之所以把旅行搞得很疲倦,也不觉得有任何审美体验,就是因为不懂得这个道理,没有在时间和空间上让自己和环境拉开足够的审美距离。

摆脱工具意识,只需要简简单单的换一座普普通通的城市即可,还有、还有像《望岳》一样,从事物的极限看过来.....


我们来玩俩个游戏 “抢 30 游戏”:

人数:俩人

规则:俩人轮流说数字只能是 "1"、"2";选择1或2开始,报数字(1、2)循环叠加值,直到30结束,说到30的那个人胜!

 

递推思考: 从1,2,3开始找规律就难了。我们假定数到30有F(30)种不同的路径,那么到达20这个数字,前一步只有两个可能的情况,即从28直接蹦到30,或者从29数到30。
由于从28蹦到30,和29到30,彼此是不同的,因此走到30的路径数量,其实就是走到28的路径数量,加上走到29的路径数量,也就是说F(30)=F(28)+F(29)。类似地,F(29)=F(28)+F(27)。这就是递推公式。
最后,F(1)只有一个可能性,就是1,F(2)有两个可能性,要么直接蹦到2,要么从1走到2。知道了F(1)和F(2),就可以知道F(3),然后再倒着推导回去,一直到F(30)即可。
数学比较好的朋友,可能已经看出来这是著名的斐波那契数列,如果我们认为F(0)也等于1,那么这个数列是这样的,1(=F(0)),1,2,3,5,8,13,21,……这个数列几乎按照几何级数的速度增长,到了F(30),就已经是 832040 了。因此,靠穷举法是不可能把所有情况想清楚的因为人不是计算机呀。

// C语言实现到90位
#include 
typedef long long int_64;

int main( int argc, const char *argv[] )
{
	int_64 f1 = 1, f2 = 1;
	for( int_64 i = 1; i <= 90; i ++ )
	{
		printf("%5d : %19ld\n%5d : %19ld\n", i++, f1, i, f2);  // 不同编译器i++实现顺序不同,可能需要改动
		f1 = f1 + f2;     // 前两个月累计
		f2 = f1 + f2;     // 同上
	}
	
	return 0;
}

要想抢到30,就需要抢到27,因为抢到了27,无论对方是加1还是加2,您都可以加到30。而要想抢到27,就要抢到24,以此类推,就必须抢到21、18、15和12、9、6、3。因此按照这种规律,只要谁抢到了3,TA就赢定了。这里面最核心的地方在于看清楚,无论对方选择 1 还是 2,你都可以让这一轮两个人加起来的数值等于3,于是您就可以牢牢控制整个过程了。

代码实现:

#include
#include
#include

int input( int t );
int copu( int s );
int main( int argc, char *argv[] )
{
    int tol = 0;
    printf("\n* * * * * * * *catch thirty* * * * * * * \n");
    printf("Game Begin\n");
    rand(); /*初始化随机数发生器*/
/*取随机数决定机器和人谁先走第一步。若为1,则表示人先走第一步*/
    if( rand()&1 )   // &1等同 % 2
        tol=input(tol);
    while(tol != 30) /*游戏结束条件*/
        if( ( tol = copu(tol) ) == 30 ) /*计算机取一个数,若为30则机器胜利*/
            printf("You lose! \n");
        else
            if( (tol=input(tol)) == 30 ) /*人取一个数,若为30则人胜利*/
            printf("computer lose! \n");
        printf(" * * * * * * * *Game Over * * * * * * * *\n");
  return 0;
}

int input( int t )
{
    int a;
do{
    printf("Please count:");
    scanf("%d", &a);
    if(a>2 || a<1 || t+a>30)
    printf("Error input,again!");
    else
        printf("You count:%d\n", t+a);
}while(a>2 || a<1 || t+a>30);
  return t+a; /*返回当前已经取走的数的累加和*/
}

int copu( int s )
{
    int c;
    printf("Computer count:");
    if((s+1)%3 == 0) /*若剩余的数的模为1,则取1*/
    printf(" %d\n",++s);
    else
        if((s+2)%3 == 0)
        {
            s += 2; /*若剩余的数的模为2,则取2*/
            printf(" %d\n",s);
        }
        else
        {
        c = (rand()&1)+1; /*否则随机取1或2, &1等同%2*/   
        s += c;
        printf(" %d\n",s);
        }
    return s;
}

小游戏,“21根火材”。玩这个我同桌请了我 10 顿饭,唉(松了一口气),最后还是我故意输给了 TA。

人数:2人

规则:俩人轮流取火材,最少取 1 根,最多取 4 根,不可多取,也不可不取,谁取最后一根火材谁输。

蕴含规律:后取者稳赢。

代码实现 :

// 21火柴游戏 - C++
#include 
using namespace std;

int main(int argc, char *argv[])
{
  int num = 21, man, computer;
  while( num != 1 )
  {
    cout<<"共有"<>man;
    if( man > 4 ||man < 1 )
      continue;  
    cout<<"\n计算机取"<<5-man<<"根火柴,还剩"<

从数学的角度来分析,为什么 后手稳赢 ?

        21 根火材,21 = 4*(1+4)+1,我们可以利用 5 这个数字。

        规则:俩人轮流取火材,最少取 1 根,最多取 4 根,不可多取,也不可不取,谁取最后一根火材谁输。

        从最简单的情况考虑,玩这个火柴游戏,最少的火柴数至少得是 5 根,因为先手如果取最大数 4,后手至少还可以取 1 根。

        假设我们是后手:

  •     先手取 1 时,我们取 4 ;
  •     先手取 2 时,我们取 3;
  •     先手取 3 时,我们取 2;
  •     先手取 4 时,我们取 1;

        那 5 根火柴时,后手取的数 = 5 - 先手取的数 ,这样就稳赢。

        现在换成 21 根火柴,道理相同:

        构成 5 这个数字,双方轮流取 4 次(4 * 5 = 20) ,就只剩下 1 根火材了,而这时取的人是后手,因此后手稳赢。

 

        某天,我被一个大学叫过去测试,其中有一个类似的题目。

  •         15 根火材俩个人轮流取 1 到 3 根,请问先取的人要取多少根绝对会赢 ?

         这个题目是在赤裸裸的干啥我们用猫腻,我想了一会说 3 根,哈哈当然对了啊。

 

         后来,我把这个问题拓展为 n 根火柴,双方轮流取,最少取 1 根,最多取 m 根,n 和 m 是随机的。

         无论是 21 还是 15 根火柴,取胜策略就是 1 + m ,一直给对手留下 1 + m 的整数倍。

                     21 = 4*(1+4) + 1

                     15 = 3*(1+3) + 3

          那 n 根火柴,双方轮流取,最少取 1 根,最多取 m 根,取到最后 1 根为胜,是不是可以写为:

                      n  =  t*(1+m) + r

           若 r = 0 [如 20 = 4*(1+4)+0],火柴数: t*(1+m),先手无论取多少,都会被后手取为 1+m ,t 次后,后手一定会拿到最后一根。

           若 r > 0 [如 21 = 4*(1+4)+1],火柴数: t*(1+m) + r,如果先取 r 个,局面就变成了 r =0,这样的局面先手才会能到最后一根火柴。

           因此,无论 r = 0 或者 r > 0,只要一直给对方留下 1 + m 的整数倍,我们就会赢。

           编程实现也很简单,这个程序只是简单的演示,并没有考虑安全性---至少函数的接收参数没有做边界检查。

// 人机博弈 
#include
#include
#include

int m, n;
void Init( );
int flag( );
void Game( int who_first );

int main( void )
{
	Init( );
	int who_first = rand( ) % 2;
	Game( who_first );
	
	return 0;
}

void Init( void )
{
	srand((unsigned int)time(NULL));
	m = rand( ) % (5-2+1)+2;            // 生成 [2, 5] 范围的随机数
        n = rand( ) % (30-15+1)+15;         // 生成 [15, 30] 范围的随机数
	printf(" 共 %d 根火柴,每轮取 [1, %d] 根\n", n, m);
	sleep(1.5);
	printf(" 游戏规则:双方轮流取,每次至少取 1 个,至多取 %d\n", m);
}

void Game( who_first )
{
	int i = 1, p1, p2;
	while( n > 0 )
	{
                // 人取  
		if( who_first || i > 1 )      
		{
			scanf("%d", &p1);
			if( p1 == 0 || p1 > n || p1 > m ){
			    puts("\n不符合条件,请重取"); continue;
		    }
			n -= p1;
			printf(" 我:> 取 (%d) 根火柴,余 (%d) \n", p1, n);
			if( n == 0 ) {
			    puts("⊙▽⊙ 博弈结束 ,您获胜了哟");
			    break;
			}
		}
                // 计算机取
		int r = n % (m+1);
		if( !r )   
		    p2 = m-1-rand( ) % (m-1);
		else
		    p2 = r;
		n -= p2;
		printf(" AI:> 取 [%d] 根火柴,余 [%d]\n", p2, n);
		if( !n ){
	    	printf("唔,[AI] 赢了");
	    	break;
		}
		i ++;
	}
}

练习·为你(递归)写诗

        我想练习即写诗。带着对递推和递归的认识漫步于诗情画意的阳光下吟吟诗,如果词汇量较少,没关系我送您一个工具 是 爬虫实现的语意分析之相关词汇 ,帮您做文字的排列组合来演绎打动人心的诗篇。

        最后把您做的诗分享在评论区吧。加油~


可递归的对象:

         集合、树、图、多边形、串、等等。如图,删除图的任意一个结点,依然是图,只不过小了。


尾部递归

         从前有座山,山里有座庙,庙里有个老和尚在讲故事讲的是,

                 从前有座山,山里有座庙,庙里有个老和尚在讲故事讲的是,

                         从前有座山,山里有个庙,庙里有个老和尚在讲故事讲的是,

                                 从前......

                                         从前......

       这是一个说明递归性质的例子,也是尾部递归的典型例子。

       其实ta表达的并不正确因为没有结束条件,这会造成死循环。


       玩探险游戏时,有这样的一些房间,每个房间都有俩入口和出口。入口可以进入下一个房间,出口可以回到上一个房间。

       现在我们从第一个房间出发,在房间中寻找藏宝图和勋章,如果在某个房间找到就原路返回。

       火精灵:金木水火土,我藏在了第 4 个房间。

  •        起点:第 1 个房间,寻找ing...
  •                        没有,进入第 2 个房间
  •                   第 2 个房间,寻找ing...
  •                        没有,进入第 3 个房间
  •                   第 3 个房间,寻找ing...
  •                        没有,进入第 4 个房间
  •                   第 4 个房间,寻找ing...
  •                        找着了带着,原路返回 ?
  •                   离开第 4 个房间
  •                   离开第 3 个房间
  •                   离开第 2 个房间
  •                   回到第 1 个房间
void search(int depth)
{
    enter(depth);
        // 进入某个房间
    find() ? back() : search(depth + 1);
        // 找着了吗
    exit(depth)
        // 退出某个房间
}

   编译器识别尾递归,在开头添加标签并在最后更新参数后,添加的 goto 语句。

   举个例子:

void print(int n) 
{ 
    if (n < 0)   
       return; 
    cout << n << endl; 
  
    print(n-1); 
} 

   编译器优化代码,

void print(int n) 
{ 
start: 
    if (n < 0)  
       return; 
    cout << n << endl; 
   
    n = n-1 
    goto start; 
} 

       在编写递归代码时,为避免栈溢出可以采用静态计数控制。

void recursive(int data) 
{ 
    static int Depth = 0; 
    if(callDepth > MAX_DEPTH) 
	    return; 

    callDepth++; 

    recursive(data); 

    Depth--; 
} 

       depth 取决于函数堆栈帧大小和最大堆栈大小。

你可能感兴趣的:(#,Computational,Thinking,算法导论)