递归

在计算机程序中,描述迭代的一种方法是使用循环,另一种完全不同的迭代实现方法就是递归。
阶乘函数(通常表示为n!)是一个经典的数学函数,它有一个固有的递归定义。
** 英式标尺具有的递归模式是分形结构的一个简单例子。**
二分查找是最重要的计算机算法之一。在一个拥有数十亿以上条目的数据集中,它能让我们有效的定位所需的某个值。
计算机的文件系统有一个递归结构,在该结构中,目录能够以任意深度嵌套在其他目录上。递归算法被广泛勇于探索和管理这些文件系统。

说明性的例子

阶乘函数

def factorial(n):
    if n == 0:
        return 1
    else:
        return n* factorial(n-1)

绘制英式标尺

def draw_line(tick_length,tick_label=''):
    line = '-'* tick_length
    if tick_label:
        line += ' '+tick_label
    print(line)

def draw_interval(center_length):
    if center_length > 0:
        draw_interval(center_length-1)
        draw_line(center_length)
        draw_interval(center_length-1)

def draw_ruler(num_inches,major_length):
    draw_line(major_length,'0')
    for j in range(1,1+num_inches):
        draw_interval(major_length-1)
        draw_line(major_length,str(j))
        
draw_ruler(1,5)
----- 0
-
--
-
---
-
--
-
----
-
--
-
---
-
--
-
----- 1

二分查找

def binary_search(data,target,low,high):
    if low > high:
        return False
    else:
        mid = (low+high)/2
        if target == data[mid]:
            return True
        elif target < data[mid]:
            return binary_search(data,target,low,mid-1)
        else:
            return binary_search(data,target,mid+1,high)

文件系统

我们考虑这样一个算法:计算嵌套在一个特定目录中的累计磁盘空间

import os

def dist_usage(path):
    total = os.path.getsize(path) # 返回由字符串路径(如:/usr/rt/courses)标识的文件或者目录使用的即时磁盘空间大小
    if os.path.isdir(path):
        for filename in os.listdir(path):
            childpath = os.path.join(path,filename)
            total += dist_usage(childpath)

    print('{0:<7}'.format(total),path)
    return total

dist_usage('D:\django')

分析递归算法

对于递归算法,我们将解释基于函数的特殊激活并且被执行的每个操作,该函数在被执行期间管理控制流。换句话说,对于每次函数调用,我们只解释被调用的主体内执行的操作数目。然后,通过在每个单独调用过程中的操作数的总和,即调用次数,我们可以解释被视为递归算法的一部分而执行的操作的总数。

计算阶乘

为了计算上面的阶乘函数factorial(n),共执行了n+1次函数调用,阶乘的每次调用执行了一个常数级别的运算。因此,我们得出这样的结论:计算factorial(n)的操作总次数是O(n),因为有n+1此函数的调用,所以每次调用占的操作数是O(1)。

绘制一个英式标尺

从n=0到5打印的行数为:
0,1,3,7,15。。。
推测输出行数为2n-1
证明:通过调用draw_interval(c)函数打印的行数比通过调用draw_interval(c-1)函数产生的行数的两倍还多1,通过归纳法,我们计算出行数1+2(2c-1-1) = 1+2c-2=2c-1

执行二分查找

我们观察到二分查找方法的每次递归递归调用中被执行的基本操作次数是恒定的。因此,运行时间与执行递归调用的数量呈正比。
对于含有n个元素的有序序列,二分查找算法的时间复杂度是O(logn)
第一次递归调用时,候选条目数是n,在进行一次二分查找调用之后,它至多是n/2;第三次调用之后,它至多是n/4;以此类推。一般情况下,在进行j次二分查找调用之后,剩下的候选条目数至多是n/2j。在最坏的情况下,当没有更多的候选条目时递归调用停止。听此,在进行递归调用最大次数,有最小整数r,使得
n/2r < 1,即r>logn。因此,二分查找算法的时间复杂度是O(logn)

计算磁盘空间使用情况

时间复杂度为O(n)
我们选择考虑在在所有递归调用中for循环迭代的总数。我们断言刚好有n-1个该循环的这种迭代。这一声明基于这样一个事实,即该循环的每次迭代进行一次对disk_usage函数的递归调用,并且已经得出结论,即对disk_usage函数共进行了n次调用(包括最初的调用)。因此,我们得出这样的结论:有O(n)次递归调用,每次递归调用在循环外部使用O(1)的时间,并且循环操作的总数为O(n)。总结这些限制条件,操作的总数是O(n)。

递归算法的不足

例如:元素唯一性问题。我们可以用下面的递归公式来确定序列中所有的n个元素是否都是唯一的。

def unique3(S,start,stop):
    if stop-start <= 1:
        return True
    elif not unique3(S,start,stop-1):
        return False
    elif not unique3(S,start+1,stop):
        return False
    else:
        return S[start] != S[stop-1]

对于一个大小为n的问题,对unique3函数的单一调用可能导致对两个大小为n-1的问题的unique3函数调用。反过来,这两个大小为n-1的调用可能又产生4个大小为n-2的调用,然后是8个大小为n-3的调用,以此类推。因此在最坏的情况下,函数调用的总数由如夏季和公式给出:
1+2+4+...+2n-1
所以函数unique3的时间复杂度为O(n2)。这个函数解决元素唯一性问题的效率如此低下。其低效率不是因为使用递归,而是缘于所使用的递归不加这样一个事实,这是我们以后要解决的问题。
一个低效的计算斐波那契数的过程

def bad_fibonacci(n):
    if n <= 1:
        return n
    else:
        return bad_fibonacci(n-2)+bad_fibonacci(n-1)

不幸的是,这样的斐波那契数公谥的直接实现会导致函数的效率非常低。以这种方式计算第n个斐波那契数需要对这个函数进行指数级别的调用。意味着bad_fibonacci(n)是的调用的总数是n的指数级。
一个高效的计算斐波那契数列的递归算法

def good_fibonacci(n):
    if n <= 1:
        return (n,0)
    else:
        (a,b) = good_fibonacci(n-1)
        return (a+b,a)

我们认为函数 good_fibonacci(n)使用的时间为O(n),每次对函数的递归调用会使参数n减小1,因此,递归追踪包括一系列的n个函数调用。整体的运算执行在O(n)的时间内完成。

Python中最大的递归深度

在递归的误用中,另一个危险的就是所谓的无线递归。
为了避免无限递归,Python的设计者做了一个有意的决定来限制可以同时有效激活的函数的总数,典型的默认值为1000.如果达到这个限制,Python解释器就生成了一个RuntimeError消息:超过最大递归深度
Python解释器可以动态的重置,以更改默认的递归限制。使用如下

import sys
old = sys.getrecursionlimit()
sys.setrecursionlimit(1000000)

递归的其他例子

如果一个递归调用最多开始一个其他递归调用,我们称之为线性递归
如果一个递归调用可以开始两个其他递归调用,我们称之为二路递归
如果一个递归调用可以开始三个或者其他递归调用,我们称之为多重递归

线性递归

元素序列的递归求和

线性递归可以作为一个有用的工具来处理数据序列,例如:python列表。
例如:计算一个含有n个整数的序列S的和

def linear_sum(S,n):
    if n == 0:
        return 0
    else:
        return linear_sum(S,n-1)+S[n-1]

对于大小为n的输入, linear_sum算法执行了n+1次函数调用,因此这将需要O(n)的时间。

使用递归逆置序列
def reverse(S,start,stop):
    if start < stop-1:
        S[start],S[stop-1] = S[stop-1],S[start]
        reverse(S,start+1,stop-1)

该递归算法过程运行时间为O(n)

用于计算幂的递归算法

即数x的n次幂问题,其中n是非负整数

def power(x,n):
    if x == 0:
        return 1
    else:
        return x * power(x,n-1)

时间复杂度为O(n)
不过有一种更快的方法用以计算幂函数,即采用了平方技术的定义。

def power(x,n):
    if n == 0:
        return 1
    else:
        partial = power(x,n//2)
        result = partial * partial
        if n % 2 == 1:
            result *= x
        else:
            return result

产生O(logn)次递归调用

二路递归

例如:用二路递归计算一个序列的元素之和

def binary_sum(S,start,stop):
    if start >= stop:
        return 0
    elif start == stop-1:
        return S[start]
    else:
        mid = (start + stop)//2
        return binary_sum(S,start,mid)+binary_sum(S,mid,stop)

递归深度为1+logn,使用O(logn)数量级的额外空间,与之前线性递归使用O(n)数量级的空间相比,这是一个进步。时间复杂度为O(n)

多重递归

对于一个文件系统磁盘空间使用状况分析的递归是多重递归的一个例子,另一个多重递归的常见应用是通过美剧各种配置来解决组合谜题的情况。

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

推荐阅读更多精彩内容