3.28《算法图解》笔记——Chapter 3 Recursive

算法图解笔记——Chapter 3 Recursive
Author: Seven Zou
Email: zoushiqi0404@gmail.com
Language: Python2.7


3 递归

本章节,也是书中作者着重讨论的部分。递归是很多算法都使用的一种编程方法。如何将问题分成基线条件和递归条件,是需要掌握的。


3.1递归

Case:借用书中的例子,假设有一手提箱,寻找底部盒子中的钥匙。
Solution 1:(while 循环: 只要盒子堆不空,就从中取一个盒子,并在其中仔细查找。)

  • 1.创建一个要查找的盒子堆
  • 2.从盒子堆取出一个盒子,在里面找。
  • 3.如果找到的是盒子,就将其加入盒子堆中,以便以后再查找。
  • 4.如果找到钥匙,则大功告成!
  • 5.回到第二步。
def look_for_key(main_box):
   pile = main_box.make_a_pile_to_look_through()
   while pile is not empty:
       box = pile.grab_a_box()
       for item in box:
           if item.is_a_box():
               pile.append(item)
           elif item.is_a_key():
               print "found the key!"

Solution 2:(递归, 函数调用自身)

  • 1.检查盒子中的每样东西。
  • 2.如果是盒子,就回到第一步。
  • 3.如果是钥匙,就大功告成!
# -*- coding: utf-8 -*-
def look_for_key(box):
    for item in box:
        if item.is_a_box():
            look_for_key(item)  # 递归
        elif item.is_a_key():
            print "found the key!"

3.2基线条件和递归条件

在递归函数调用自身时,容易导致无限循环。e.g.如下编写倒计时的函数。运行之后,程序会一直运行。

# -*- coding: utf-8 -*-
# objective: 3...2...1
def countdown(i):
    print i
    countdown(i-1)

所以需要加入条件避免形成无限循环,基线条件。所以每个递归函数都包含 基线条件(Base Case)递归条件(Recursive Case)递归条件 指的是函数自身,而 基线条件 指的是函数不再调用自身。针对上述Case,可以改进。

# -*- coding: utf-8 -*-
def countdown(i):
    print i
    if i <= 0:  # 基线条件
        return 
    else:       # 递归条件
        countdown(i-1)
Input:  countdown(3)
Output: 3
        2
        1
        0

3.3 栈

3.3.1 调用栈
  • 调用栈(Call Stack),一个重要的编程的概念。前面学了数组和链表,针对这两个操作而言,可以有 插入删除读取
# -*- coding: utf-8 -*-
def greet(name):    # 这个函数问候用户,再调用另外两个函数。
    print "hello,"  + name +  "!"
    greet2(name)
    print "getting ready to say bye ..."
    bye()


def greet2(name):
    print "how are you, " + name + "?"


def bye():
    print "ok bye!"
Input:  greet("Steven")
Output: hello,Steven!
        how are you, Steven?
        getting ready to say bye ...
        ok bye!

计算机在内部使用被称为调用栈的 。 上述调用函数时发生如下过程:

  • 1.假设调用greet("Steven),计算机首先为该函数调用分配一块内存。
  • 2.我们来使用这些内存。变量name被设置为Steven,这需要存储到内存中。
  • 3.接下来,打印hello,Steven!,再调用greet2("Steven)。同样,计算机也为这个函数调用分配一块内存。
  • 4.计算机使用一个栈来表示这些内存块,其中第二个内存卡位于第一个内存卡上面。打印how are you, Steven?,然后从函数调用返回。此时,栈顶的内存块被弹出。
  • 5.现在栈顶的内存块是函数greet("Steven)的,即 又返回到了函数greet("Steven),当调用函数greet2("Steven)时,函数greet("Steven)只执行了一部分(调用另一个函数时,当前函数暂停并处于未完成状态。)
    该函数的所有变量的值还存储在内存中。执行完函数greet2("Steven)后,回到函数greet("Steven),并从离开的地方开始接着往下执行:首先打印getting ready to say bye ...,再调用函数bye
  • 6.在栈顶添加函数bye的内存块。然后,打印ok bye!,并从这个函数返回。
  • 7.现在又返回到了函数greet。由于没有别的事情要做,就从函数greet返回。

这个栈用于存储多个函数的变量,被称为调用栈。


Remark:根据个人理解,整个过程很像本科阶段学习单片机的_中断_功能。在以前给本科新生介绍_中断_时常用的例子就是“要去烧水时门铃响”。
例子场景:你正准备拿着水壶去烧水,此刻门铃响,放下水壶去开门,开门结束后返回烧水的动作。不知比喻的恰不恰当,只是个人进行类比的理解。


3.3.1 递归调用栈

递归函数也使用 调用栈。 递归函数factorial(5)写作 5 ! 5! 5!( 5 ! = 5 × 4 × 3 × 2 × 1 5!=5 \times 4 \times 3 \times 2 \times 1 5!=5×4×3×2×1)。其Code如下:

def fact(x):
    if x == 1:
        return  1
    else
        return x * fact(x-1)

可以类比上述过程,就不作叙述了。


Reference

[美]Aditya Bhargava/袁国忠, 算法图解, 北京:人民邮电出版社, 2017.3.


附个人Github地址: https://github.com/shiqi0404/Algorithm_Diagram,其中包括笔记、Code还有书本pdf版。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值