递归算法

To iterate is human, to recurse, divine.
人理解迭代,神理解递归。

什么是递归

递归算法(英语:recursion algorithm)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。

简单地说,就是如果在函数中存在着调用函数本身的情况,这种现象就叫递归。

「递归」,先有「递」再有「归」,「递」的意思是将问题拆解成子问题来解决, 子问题再拆解成子子问题,...,直到被拆解的子问题无需再拆分成更细的子问题(即可以求解),「归」是说最小的子问题解决了,那么它的上一层子问题也就解决了,上一层的子问题解决了,上上层子问题自然也就解决了

递归的两个条件:

  • 可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式。(自身调用)
  • 存在一种简单情境,可以使递归在简单情境下退出。(递归出口)

递归算法的一般形式:

def f(mode):
    if end_condition:  # 递归出口
        end
    else:
        f(mode_small)  # 递归本身,递归

阶乘

一个数的阶乘是递归简单而典型的例子,阶乘的递推公式为:factorial(n)=n*factorial(n-1),其中n为非负整数,且0!=1,1!=1

def factorial(i):
    """阶乘"""
    if i < 0:
        return
    elif i <= 2:
        return i
    else:
        return i * factorial(i-1)

递归过程

递归

这个调用的过程和栈的工作原理一样,递归调用就是通过栈这种数据结构完成的。整个过程实际上就是一个栈的入栈和出栈的问题。

递归中的"递"就是入栈,"归"就是出栈。

入栈和出栈 的过程:

def 递归(n):
    print("递进:" + str(n))
    if n > 0:
        递归(n-1)
    print("回归:" + str(n))
递归(3)

output:

递进:3
递进:2
递进:1
递进:0
回归:0
回归:1
回归:2
回归:3

递归过程:

image

递归算法解决思路

套路:

  1. 明确你这个函数想要干什么
  2. 寻找递归结束条件
  3. 找出函数的等价关系式(递归中最难的一步)

经典例题

1. 斐波那契数列

形如 1、1、2、3、5、8、13、21、34 ...的数列

  1. 由这个数列我们可以容易发现其递推公式:f(n)=f(n-1)+f(n-2)
  2. 递归结束条件:当n=1或者n=2时,f(1)=f(2)=1
def fibonacci(i):
    """斐波那契数列"""
    if i <= 2:
        return 1
    else:
        return fibonacci(i-1) + fibonacci(i-2)

2.猴子吃桃子

猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个,第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃前一天剩下的一半零一个。到第10天早上想再吃时,见只剩下一个桃子了。求第一天共摘多少个桃子?

  1. 最后一天时,只剩下一个桃子 (结束条件)
  2. 当天的桃子等于上一天加一的和乘二 (monkey(n-1)+1)*2
def monkey(n):
    """猴子分桃"""
    if n == 1:
        return 1
    else:
        return (monkey(n-1)+1)*2

3.汉诺塔问题

汉诺塔

相传在古印度圣庙中,有一种被称为汉诺塔(Hanoi)的游戏。该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如下图)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。

解题方法:

  1. 将最上面的n-1个圆盘从初始位移动到过渡位

  2. 将初始位的最底下的一个圆盘移动到目标位

  3. 将过渡位的n-1个圆盘移动到目标位

  • 当只剩下一个盘子时,直接移动到C (结束条件)
  • 将n-1只盘子看成整体,通过c移动到b,然后a移动到c,最后b处的n-1只盘子经过a移动到c
def hanoi(n, a, b, c):  # a为初始位,b为过渡位,c为目标位
    """汉诺塔"""
    if n == 1:
        print(a, '-->', c)
    else:
        hanoi(n-1, a, c, b)  # 初始位为a,通过c移动到b
        print(a, '-->', c)
        hanoi(n-1, b, a, c)  # 初始位为b,通过a移动到c

4. 反转单链表

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
  1. 结束条件:当链表为空表或者只有一个节点
  2. 递的操作:
    1.得到尾部节点:new_head = self.reverseList(head.next)
    2.翻转当前节点:head.next.next = head
    3.拆掉当前节点的next:head.next = None
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None


class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        """反转单链表"""
        if head is None or head.next is None:
            return head
        new_head = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return new_head

适用递归的问题

  1. 数据的定义是按递归定义的。如Fibonacci函数。
  2. 问题解法按递归算法实现。如Hanoi问题。
  3. 数据的结构形式是按递归定义的。如二叉树、广义表等。

递归和递推的异同

递推和递归有着很多的相似之处,递推甚至可以看做是递归的反方向,但对比其细节是存在很多不同的。

递归法:把问题转化为规模更小的子问题解决,思考的重点在于建立原问题和子问题之间的联系。有的问题有很明确的递归结构,但是需要仔细的思考,才能正确的转化为结构相同的子问题。

递推法:根据已知信息不断的计算出未知信息,直到得到结果,思考的重点在于“步步为营”。

尾递归

尾递归相对传统递归,其是一种特例。在尾递归中,先执行某部分的计算,然后开始调用递归,所以你可以得到当前的计算结果,而这个结果也将作为参数传入下一次递归。这也就是说函数调用出现在调用者函数的尾部,因为是尾部,所以其有一个优越于传统递归之处在于无需去保存任何局部变量,从内存消耗上,实现节约特性。

普通递归调用:

def recursion(n):
    if n==1:
        return n
    else:
        return n+recursion(n-1)  

调用这个函数recursion(5),编译器会执行:

recursion(5)
5+recursion(4)
5+(4+recursion(3))
5+(4+(3+recursion(2)))
5+(4+(3+(2+recursion(1))))
5+(4+(3+(2+1)))
15

此处编译器会分配递归栈来保存中间结果下来看尾递归实现:

 def tail_recursion(n,total=0):
        if n==0:
            return total
        else:
            return tail_recursion(n-1,  total+n)  

此时,编译器做的工作:

tail_recursion(5,0)
tail_recursion(4,5)
tail_recursion(3,9)
tail_recursion(2,12)
tail_recursion(1,14)
tail_recursion(0,15)
15

你可以看到当前时刻的计算值作为第二个参数传入下一个递归,使得系统不再需要保留之前计算结果。

但是python本身不支持尾递归(没有对尾递归做优化),而且对递归的次数有限制,当递归深度超过1000时,会抛出异常。

总结

递归是非常基础的算法,思考递归时要抛弃程序设计的细节

  1. 明确递归函数功能

  2. 递归程序出口设计

  3. 递归要有规模递减

递归思想在动态规划,回溯算法,二叉树的深度优先搜索等都有密切的涉及。

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

推荐阅读更多精彩内容