理解递归

从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……’”

读完这段话你是否有一种晕头转向的感觉? 或许你已经发现其中的一些端倪。 这是一种层层嵌套和结构相似的形式,在数学和计算机领域把这种形式叫做「递归」

除此之外在生活中也有很多「递归」的存在,比如查字典的过程:遇到一个生词 "xx" 找一本字典查找定义,但是在定义又遇到了生词 "yy1", "yy2" , 因此我们继续查找生词 "yy1", "yy2" 的定义,直到所有的定义中不出现生词为止。

神奇的贝壳,花菜的纹络符合斐波纳契数(Fibonacci),排列形成一种螺旋式结构,这也是一种递归的形式。

Fibonacci
Fibonacci

递归是一种解决复杂编程问题的有效方法,在计算机领域有着极其广泛的应用。其特点是表达形式简洁优雅,一开始理解起来却非常困难,需要通过大量的练习,反复思考才能掌握递归的精髓。

定义


递归 (recursion )是一个函数定义中调用函数本身。通常用于把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

一个例子


双倍

双倍问题:

假设有一碗M&M糖,我们不知道有多少颗糖在碗中 ,通过不用数数字的方法来增加一倍糖的数量,也就是说把糖的数量变成原来的两倍。

前提条件:

1: 有一群人,人数超过糖的数量一起来完成这个任务。
2: 旁边有一麻袋的M&M糖可供使用,数量远远超过一碗M&M糖。

如果可以一个一个得数,解决这个问题的方法显而易见,先数出糖的数量,然后从旁边的麻袋中拿出同等数量即可。但是不能用这个方法,我们就要换一个思路来解决这个问题。

暂停几分钟,认真思考一下这个问题。

解决方法是: 把这群人排个队,然后告诉每个人记住一个规则。

双倍糖数量规则:
如果 碗中 没有 糖了,把碗传递给之前那个人。
否则的话,从碗中取出一颗糖,然后传递给队伍中后一个人,告诉他使用这个 双倍糖数量规则。
当碗传回来的时候,放入2颗糖当碗中,然后把碗传给队伍中前一个人。

示意图

以上示意图假设碗中只有4颗糖,通过一个分工合作的流程, 碗从左往右传递时候每个人取出碗中的一颗糖,当碗空了以后,把碗传递回来每个人把原来的那颗糖放回碗中,并且从麻袋中取一颗糖再放入碗中。 我们可以看到,每个人递归使用一个简单的规则,即使人不会数数也可以完成双倍的任务。

通过以上例子,相信应该能加深对递归的认识。

递归使用


递归实际上是通过将一个大的问题不断地分解成规模更小的子问题,并且大问题和小问题的解决方法是一样的。直到归约成一个可以直接得到子答案问题,然后返回合并得到计算结果。

在实际开发中,递归程序有一个通用的格式, 分别处理两种情况:

  1. 基准情况(Base Case),也称递归基。
    有效递归定义的关键是具有终止条件,使得到达某一点后不再递归,否则会导致无穷递归。

  2. 递归情况, 不能直接返回结果,需要依赖次一级的递归调用才能得出计算结果。

例如 求阶乘的例子:
def factorial(n):
    if n == 0: #递归基
        return 1
    else:
        return n * factorial(n-1) #递归情况

递归练习


0. 如何逆向显示一个 嵌套结构的列表?

[1,[2,3],4,[5,6,[7,8],9]]

def reverse_list(alist):
    if  alist    != []:
        reverse_list(alist[1:])
        if  type(alist[0]) == list:
            reverse_list(alist[0])
        else:
            print(alist[0],end="   ")
      
    
list1  =  [1,[2,3],4,[5,6,[7,8],9]]
reverse_list(list1)

[out]: 9  8  7  6  5  4  3  2  1 

1. 判断一个字符串是否是回文(左右对称的字符串)。

如:
“Java” -> False
“madam” -> True
“Q” -> True
"byebye" -> False

def isPalindrome(astr):
    if len(astr)<=1:
        return True
    else:
       return   (astr[0]   ==  astr[-1]) and isPalindrome(astr[1:-1])


isPalindrome('madam')
[out]: True
isPalindrome('Java')
[out]: False

2. 文件内容翻转

file_content   =  '''
Roses are red,
Violets are blue,
Sugar is sweet,
And so are you.
'''.split(',')

def flip(pm):
    if len(pm)<=1:
        return pm
    else:
        #swap head tail  
        return [pm[-1]]+flip(pm[1:-1])+[pm[0]]
flip(poem)  
 
[out]: ['And so are you.',
 'Sugar is sweet',
 'Violets are blue',
 'Roses are red']

3. 打印一个整数的二进制形式

def print_binary(n):
    if n < 0 :
        print("-", end='')
        print_binary(-n)
    elif   n ==    0 or n    ==1:
        print(int(n), end='')
    else:
        last   =   n   % 2
        rest =  int(n / 2)
        
        if rest > 0:
            print_binary(rest)
        print(last, end='')
        
 
print_binary(5)   
[out]: 101

4.Hanoi 塔问题是用递归求解的经典案例。

将 n 个圆 盘从 A 柱移到 C 柱,移动过程中可以利用 B 柱作为临时存放柱。
具体的移动圆盘的规则是:

  1. 圆盘只能放在柱子上;
  2. 每次只能移动位于任一柱子顶部的圆盘;
  3. 大圆盘永远不能置于小圆盘之上;

核心解法是:

利用第三个柱子作为临时存放点,把 n-1个盘先移动到临时存放点,然后把最大的盘子移动到目标柱子,直到只剩下一个盘子的情况直接把盘子从源柱子移动到目标柱子。

初始状态
递归缩小规模

这个思路使用递归来表达非常方便。

def  hanoi(n,source,target,temp):
    if n == 1:
        print("{0}  {1} -> {2} ".format(1, source,target))
    else:
        #   move   n-1  to  temp
        hanoi(n-1,source,temp,target)
        # move  n  to  target 
        print("{0}  {1} -> {2} ".format(n, source,target))
        #move n-1 to target
        hanoi(n-1,temp,target,source)
        
hanoi(3,"A","C","B")

'''
[out]: 
1  A -> C 
2  A -> B 
1  C -> B 
3  A -> C 
1  B -> A 
2  B -> C 
1  A -> C 

最后


递归是一种强大的算法设计思想,利用递归可以轻松解决很多难题。
虽然递归算法容易设计实现,也容易理解,但递归是有代价的。由于递归涉及大量的函数调用,因此需要耗费较多的内存和较长的执行时间,过多的递归调用导致程序出现stackoverflow栈溢出 异常, 一般可以通过循环(while ,foor-loop)来优化。

参考

1: CS 106B
2: 维基百科

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 计算机科学的新学生通常难以理解递归程序设计的概念。递归思想之所以困难,原因在于它非常像是循环推理(circular...
    启明_b56f阅读 7,334评论 0 20
  • 递归真是个奇妙的思维方式。自打我大二学习递归以来,对一些简单的递归问题,我总是惊叹于递归描述问题和编写代码的简洁。...
    紫松阅读 26,655评论 13 92
  • 所有的计算都是递归;要理解递归首先要理解递归。 程序设计思想之一“递归”历来是同学们的理解难点。据说,**要理解递...
    Bintou老师阅读 1,014评论 0 3
  • 1 递归的思想 以此类推是递归的基本思想。具体来讲就是把规模大的问题转化为规模小的相似的子问题来解决。在函数实现时...
    Bobby0322阅读 3,510评论 0 6
  • 向大家分享建立有价值的人际关系要遵循的6个准则。 第一,向对方表示最真诚的欣赏和感谢。在沟通中要全身心投入,让对方...
    有杕之杜阅读 329评论 1 1