SICP— 第一章 构造过程抽象
SICP Structure And Interpretation Of Computer Programs 中文第2版
分两部分 S 和 I
第一章 构造过程抽象
1,程序设计的基本元素
2,过程与他们所产生的计算
3, 用高阶函数做抽象
第二章 构造数据抽象
第三章 模块化、对象和状态
第四章 元语言抽象
第五章 寄存器机器里的计算
(心智的活动,学习。1,组合 简单认识组 为一个符合认识由此产生复杂认识。2,对比 两个认识放在一起对比,得到有关于相互关系的认识。3,将之隔离与其他认识,即抽象)
1,程序设计的基本元素:
1,表达式
2,命名和环境
3,组合式的求值
4,复合过程
5,过程应用的代换模型
(define (square x) (* x x)
应用序:先求值参数而后应用的方式,(自上而下)
正则序:完全展开而后归约
练习1.3 请定义一个过程,它以三个数为参数,返回其中较大的两个数之和
平方和
计算平方和的函数可以在书本第 8 页找到:
;;; p8-sum-of-squares.scm
(define (sum-of-squares x y)
(+ (square x)
(square y)))
判断大数
『判断三个数中较大的两个』这个任务实际上就是执行以下决策树(假设三个数分别为 x 、 y 和 z):
x < y
/ \
/ \
/ \
x < z y < z
/ \ / \
/ \ / \
x < y x < y y < x y < x
x < z z < x y < z z < y
这个决策树可以用 if 来表示:
(if (> x y)
; x 较大
(if (> y z)
; x 和 y 较大
; x 和 z 较大)
; y 较大
(if (> x z)
; y 和 x 较大
; y 和 z 较大))
也可以使用 cond :
(cond ((and (> x y) (> y z))
; x 和 y 较大)
((and (> x y) (> z y))
; x 和 z 较大)
((and (> y x) (> x z))
; y 和 x 较大)
((and (> y x) (> z x))
; y 和 z 较大))
另一种办法是,写出 bigger 和 smaller 两个函数,它们分别接受两个数作为参数, bigger 返回两数的较大者,而 smaller 返回两数的较小者:
;;; 3-bigger-and-smaller.scm
(define (bigger x y)
(if (> x y)
x
y))
(define (smaller x y)
(if (> x y)
y
x))
通过 bigger 和 smaller ,可以用一种更抽象的方式来选出三个数中较大的两个:
(define bigger (bigger x y)) (define another-bigger (bigger (smaller x y) z))
bigger-sum-of-squares
综合以上这些工作,就可以将整个函数写出来了:
;;; 3-bigger-sum-of-squares.scm
(load "p8-sum-of-squares.scm")
(load "3-bigger-and-smaller.scm")
(define (bigger-sum-of-squares x y z)
(sum-of-squares (bigger x y) ; bigger
(bigger (smaller x y) z))) ; another bigger
bigger-sum-of-squares 接受三个数,并且计算较大两数的平方和。
1.7 实例:采用牛顿法求平方根
说明性描述what 与 行动性描述how
牛顿逐步逼近法

行动性描述 过程性语言
(define (sqrt-iter guess x)
(if (good-enough? guess x)
guess
(sqrt-iter (improve guess x)
x)))
部分:
(define (improve guess x)
(average guess (/ x guess)))
(define (average x y)
(/ (+ x y) 2))
(define (good-enough? guess x)
(< (abs (- (square guess) x)) 0.001))
练习1.6
使用cond 将if定义为一个常规过程
(define (new-if predicate then-clause else-clause)
(cond (predicate then_clause)
(else else-clause))
试猜测用这个new-if重写求平方根的程序会发生什么?
(define (sqrt-iter guess x)
(new-if (good-enough? guess x)
guess
(sqrt-iter (improve guess x)
x)))
答案:返回: ;Aborting!: maximum recursion depth exceeded
超出了解释器最大递归深度
原因 使用new-if函数,无论predicate是真还是假,then-clasuse 和else-clause两个分支都会被求值,使用new-if重定义sqrt-iter,无论测试结果如何,sqrt-iter都会一直递归下去
练习1.7
使用good-enough?对于大数和小数会失败,解决策略是监测猜测值从上一次迭代到下一册迭代的变化情况,解释原因
答案:
可以发现,对于特别小的数,比如 0.00009 ,书本给出的 sqrt 并不能计算出正确的答案; 而对于特别大的数,因为 mit-scheme 实现的小数精度不足以表示两个大数之间的差,所以 sqrt 会陷入死循环而无法得出正确结果。
要避免这一错误,我们按照练习所说,对 good-enough? 进行修改:不再检测猜测值 guess 的平方与 x 之间的差,而是检测新旧两次猜测值之间的比率,当比率变化非常小时,程序就停止 improve 。
新的 good-enough? 定义如下:
;;; 7-good-enough.scm
(define (good-enough? old-guess new-guess)
(> 0.01
(/ (abs (- new-guess old-guess))
old-guess)))
使用新的 good-enough? 重新定义 sqrt 函数 —— 大部分的代码和原来的一样,只是 sqrt-iter 和 good-enough? 两个函数更改了:
;;; 7-sqrt.scm
(load "p16-sqrt.scm") ; 一定要先载入这些函数
(load "p15-improve.scm") ; 确保后面定义的重名函数可以覆盖它们
(load "p15-average.scm")
(load "7-good-enough.scm") ; 载入新的 good-enough?
(define (sqrt-iter guess x)
(if (good-enough? guess (improve guess x)) ; 调用新的 good-enough?
(improve guess x)
(sqrt-iter (improve guess x)
x)))
新的 sqrt 函数对于非常小和非常大的值都能给出正确答案:
1 ]=> (load "7-sqrt.scm") ;Loading "7-sqrt.scm"... ; Loading "p16-sqrt.scm"... ; Loading "p15-sqrt-iter.scm"... ; Loading "p15-good-enough.scm"... done ; Loading "p15-improve.scm"... ; Loading "p15-average.scm"... done ; ... done ; ... done ; ... done ; Loading "p15-improve.scm"... ; Loading "p15-average.scm"... done ; ... done ; Loading "p15-average.scm"... done ; Loading "7-good-enough.scm"... done ;... done ;Value: sqrt-iter 1 ]=> (sqrt 0.00009) ;Value: 9.486833049684392e-3 1 ]=> (sqrt 900000000000000000000000000000000000000000000000000000000000000000000000000000000000) ;Value: 9.486982144406554e41
Note
在新的 sqrt-iter 的定义中, (improve guess x) 被重复调用了多次,这是因为书本到这个练习为止还没有介绍 let 结构。
以下是一个使用 let 结构重写的、无重复调用的 sqrt-iter :
(define (sqrt-iter old-guess x)
(let ((new-guess (improve old-guess x)))
(if (good-enough? old-guess new-guess)
new-guess
(sqrt-iter new-guess x))))
练习1.8例题使用此公式:(x/(y^2) + 1) / 2 < 精确值若使用此公式求立方根,给出过程
原例题 (define (new-sqrt guess x) (if (good-enough? guess x) guess (sqrt-iter (improve guess x) x)))
(define (improve guess x) (tri-average (* 2 guess) (/ x guess)))good-enough里改为cube即可
1.8 过程作为黑箱抽象
分解使每一个过程可以被用作定义其他过程的模块例如,基于square定义过程good-enough?,就是将square看做一个“黑箱”square纪委几个过程的抽象,即过程抽象过程用户不必去关心的细节:局部名: 形式参数的具体名字对过程体完全没有关系,称约束变量内部定义和块结构: 嵌套
称为块结构 将x作为内部定义中的自由变量,x有实际参数得到自己的值,称为词法作用域
2,过程与他们所产生的计算
过程所产生的计算过程的“形状” 及所消耗各种重要计算资源(时间空间)的速率 1,线性的递归和迭代阶乘 n!(define (factorial n) (if (= n 1) 1 (* n (factorial (- n 1)))))![]()
递归:在展开阶段里, 这一计算过程构造起一个推迟进行的操作所形成的两条,收缩阶段表现为这些运算的实际执行这种类型的计算过程又一个推迟记性的运算链条刻画,称为一个递归计算过程推迟执行的乘法链条的长度(即位保存其轨迹需要保存的信息量)随n值而线性增长(正比于n) 过程是递归的,说明这个过程的定义中(直接或间接)引用了该过程本身 迭代: 可以用状态变量描述尾递归:在常量空间中执行含有迭代型计算过程的递归过程 练习 1.9inc:将参数增加1dec:将参数减少1(define (+ a b) (if (= a 0) b (inc (+ (dec a) b))))(define (+ a b) (if (= a 0) b (+ (dec a) (inc b))))请用代换模型展示这两个过程在求值(+ 4 5)时所产生的计算过程。区分是迭代还是递归》答案: 第一个调用本身了,是递归过程,计算过程是迭代
(plus 3 5)进行求值,表达式的展开过程为:
(plus 3 5) (inc (plus 2 5)) (inc (inc (plus 1 5))) (inc (inc (inc (plus 0 5)))) (inc (inc (inc 5))) (inc (inc 6)) (inc 7) 8
从计算过程中可以很明显地看到伸展和收缩两个阶段,且伸展阶段所需的额外存储量和计算所需的步数都正比于参数 a ,说明这是一个线性递归计算过程。
另一个+函数的定义如下(为了和内置的+区分开来,我们将函数改名为plus):
;;; 9-another-plus.scm
(load "9-inc.scm")
(load "9-dec.scm")
(define (plus a b)
(if (= a 0)
b
(plus (dec a) (inc b))))
对 (plus 3 5) 进行求值,表达式的展开过程为:
(plus 3 5) (plus 2 6) (plus 1 7) (plus 0 8) 8 从计算过程中可以看到,这个版本的plus函数只使用常量存储大小,且计算所需的步骤正比于参数a,说明这是一个线性迭代计算过程。
练习 1.10Ackermann函数(define (A x y) (cond ((= y 0) 0) ((= x 0) (* 2 y)) ((= y 1) 2) (else (A (- x 1) (A x (- y 1))))))
1 ]=> (load "10-ackermann.scm") ;Loading "10-ackermann.scm"... done ;Value: a 1 ]=> (A 1 10) ;Value: 1024 1 ]=> (A 2 4) ;Value: 65536 1 ]=> (A 3 3) ;Value: 65536
接着,我们通过给定一些连续的 n ,来观察题目给出的几个函数,从而确定它们的数学定义。
首先是 (define (f n) (A 0 n)) :
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... |
|---|---|---|---|---|---|---|---|---|---|
| 值 | 0 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | ... |
可以看出,函数 f 计算的应该是 2* n
然后是 (define (g n) (A 1 n)) :
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... |
|---|---|---|---|---|---|---|---|---|---|
| 值 | 0 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | ... |
可以看出,函数 g 计算的应该是2的n次方
最后是 (define (h n) (A 2 n)) :
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... |
|---|---|---|---|---|---|---|---|---|---|
| 值 | 0 | 2 | 4 | 16 | 65536 | ... | ... | ... | ... |
超过4之后,再调用函数h就会出现超出解释器最大递归深度的现象 1.2.2树形递归 Fibonacci数序列 每个数都是前面两个数之和第一种方法,递归法,翻译定义 (define (fib n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2))))))
此函数Fib(n)值的增长相对于n是指数的,Fib(n)就是最接近 (Φ^n) / √5 的整数
Φ = (1 + √5) / 2 =1.6180.... 即黄金分割的值
Φ^2 = Φ + 1
即该过程所用的计算步骤将随着输入增长而指数性的增长
空间需求是随着输入增长而线性增长
这样在计算中的每一点,只需保存书中在此之上的结点的轨迹
一般说,树形递归计算过程里所需的步骤数将正比与树中的结点数,其空间需求正比于数的最大深度。
第二种,迭代方法
int a b ,初始化为Fib(1) = 1 和 Fib(0) = 0
迭代规则:a = a + b
b = a
n次变后,a和b 分别等于Fib(n + 1) 和 Fib(n)
即:
(define (fib n)
(fib-iter 1 0 n))
(define (fib-iter a b count)
(if ( = count 0)
b
(fib-iter ( + a b) a (- count 1))))
第二种方法相对于n为线性的
实例:换零钱方式的统计
美元换为 半美元、四分之一美元、10美分、5美分 和 1美分
将总数为a的现金换成 n 种硬币的不同方式的数目等于
甲。将现金数 a 换成除第一种之外的所有其他硬币的不同方式数目,加上
乙。将现金 a-d 换成所有种类的硬币的不同方式数目,其中的 d 是第一种硬币的币值
a = 0 , 1种换零钱的方式
a < 0 , 0种
n = 0 , 0种
(define (count-change amount)
(cc amount 5))
(define (cc amount kinds-of-coins)
(cond ((= amount 0) 1)
((or (< amount 0) (= kinds-of-coins 0)) 0)
(else (+ (cc amount ; n次之后 甲数目
(- kinds-of-coins 1))
(cc (- amount ; n次之后 乙数目
(first-denomination kinds-of-coins))
kinds-of-coins)))))
(define (first-denomination kinds-of-coins)
(cond ((= kinds-of-coins 1) 1)
((= kinds-of-coins 2) 5)
((= kinds-of-coins 3) 10)
((= kinds-of-coins 4) 25)
((= kinds-of-coins 5) 50)))
练习1.11
函数 f 由如下的规则定义;如果 n < 3, 那么f (n) = n; 如果n>=3,那么f(n) =f(n-1)+2f(n-2) + 3f(n-3)
写出一个采用递归计算过程计算 f 的过程 和 一个采用迭代计算过程计算 f 的过程
递归:recursion
(define (f n)
(cond (< n 3) n)
(>= n 3)
(+ f(- n 1)
(* 2 f(- n 2)
(* 3 f(- n 3))))))
迭代:iteration
(define (ff n)
(+ 2 (* 2 (f 1)) (* 3 (f 0)))..
解题集答案:
递归:
(define (f n)
(if (< n 3)
n
(+ (f (- n 1))
(* 2 (f (- n 2)))
(* 3 (f (- n 3))))))
迭代:
要写出函数f 的迭代版本,关键是在于看清初始条件和之后的计算进展之间的关系,
根据函数 f 的初始条件 “如果 n < 3,那么f(n) = n ",有等式
f(0) = 0
f(1) = 1
f(2) = 2
当 n >= ,有f(n) = f(n - 1) + 2f(n - 2) + 3f(n - 3)

迭代版函数定义如下:它使用 i 作为渐进下标, n作为最大下标, a、b和c三个变量分别代表函数调用 f (i + 2) 、f(i + 1)、f(i),从f(0) 开始计算
(define (f n)
(f-iter 2 1 0 0 n))
(define (f-iter a b c i n)
(if (= i n)
c
(f-iter (+ a (* 2 b) (* 3 c)) ; new a
a ; new b
b ; new c
(+ i 1)
n)))
效率对比:
递归版本的函数 f 有很多多余的计算,例如需要计算很多次f(1)、f(2)
递归版本的 f 函数是一个指数级复杂度的算法
迭代使用三个变量 f(n - 1), f(n - 2), f(n - 3) 的值,使用自底向上的计算方式进行计算,因此迭代版本没有多余的重复计算工作,它的复杂度正比n,是一个线性迭代函数。
练习 1.12
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
。。。。。
Write a procedure that computes elements of Pascal's triangle by means of a recursive process.
三角形边界的数都是1,内部的每个数是位于它上面的两个数之和。
请写一个过程,采用递归计算过程 计算出帕斯卡三角形的各个元素。
答案:
(define (Pascal n)
(cond (= (/ n 2) 0)
((= n 2)
(define (Pascal x y)
(+ (Pascal (- x 1) (- y 1)) (Pascal (- x 1) y)
解题集:
如果使用 pascal(row, col) 代表第 row 行和第 col 列上的元素的值,可以得出一些性质:
- 每个
pascal(row, col)由pascal(row-1, col-1)(左上边的元素)和pascal(row-1, col)(右上边的元素)组成。 - 当
col等于0(最左边元素),或者row等于col(最右边元素)时,pascal(row, col)等于1。
- 每个
比如说,当 row = 3 , col = 1 时, pascal(row,col) 的值为 3 ,而这个值又是根据 pascal(3-1, 1-1) = 1 和 pascal(3-1, 1) = 2 计算出来的。
综合以上的两个性质,就可以写出递归版本的 pascal 函数了:
(define (pascal row col)
(cond ((> col row)
(erroe "unvalid col value"))
((or (= col ) (= row col))
)
() (- col ))
(pascal (- row ) col)))))
前面给出的递归版本 pascal 函数的效率并不好,首先,因为它使用的是递归计算方式,所以它的递归深度受限于解释器所允许的最大递归深度。
其次,递归版本 pascal 函数计算值根据的是公式
这个公式带有重复计算,并且复杂度也很高,因此,前面给出的 pascal 函数只能用于 row 和 col 都非常小的情况。
要改进这一状况,可以使用帕斯卡三角形的另一个公式来计算帕斯卡三角形的元素
,其中表示的阶乘(factorial)
阶乘:factorial(跌代版)
(define (factorial n)
(fact-iter 1 1 n))
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
跌代版的pascal定义:
(define (pascal row col)
(/ (factorial row)
(* (factorial col)
(factorial (- row col)))))
迭代版的 pascal 函数消除了递归版 pascal 的重复计算,并且它是一个线性复杂度的算法
因此,
迭代版的 pascal 比起递归版的 pascal 要快不少,并且计算的值的大小不受递归栈深度的控制:

1.2.3 增长的阶
目的:描述不同的计算过程在消耗资源的速率上可能存在着巨大差异。
计算阶乘: 计算过程所需步骤的增长 空间需求的增长
递归: O(n) O(n)
迭代: O(n) O(1)(即为一个常数)
斐波那契计算
树形递归 O(黄金分割率的 n 次方) O(n)
练习1.15

a:
p是翻译的定义,限制为(> (abs angle) 0.1))
每递归一次,angle \ 3.0
12.15 \ 3.0
4.05
1.35
0.45
0.15
0.05
五次
b:
在求值(sine a)的时候,a每次都被除以 3.0 , 而sine是一个递归程序,因此它的时间和空间复杂度都是 O(log a)。 (由O(log3(a/0.033333 = log3(30a) = log3(30) + log3(a), log3(30)是常数,因此是O(log a)
即每当 a 增大一倍(准确的说是乘因子 3),p的运行次数就会增加一次。
1.2.4 求幂
SICP— 第一章 构造过程抽象的更多相关文章
- [蛙蛙推荐]SICP第一章学习笔记-编程入门
本书简介 <计算机程序的构造与解释>这本书是MIT计算机科学学科的入门课程, 大部分学生在学这门课程前都没有接触过程序设计,也就是说这本书是针对编程新手写的. 虽然是入门课程,但起点比较高 ...
- SCIP:构造过程抽象--面向对象的解释
心智的活动,除了尽力产生各种简单的认知之外,主要表现为如下三个方面:(1)将若干简单认知组合为一个复合的认识,由此产出各种复杂的认知.(2)将两个认知放在一起对照,不管他们如何简单或者复杂,在这样做时 ...
- 第一章 MYSQL的架构和历史
在读第一章的过程中,整理出来了一些重要的概念. 锁粒度 表锁(服务器实现,忽略存储引擎). 行锁(存储引擎实现,服务器没有实现). 事务的ACID概念 原子性(要么全部成功,要么全部回滚). 一致性 ...
- 从结构和数字看OO——面向对象设计与构造第一章总结
不知不觉中,我已经接触OO五周了,顺利地完成了第一章节的学习,回顾三次编程作业,惊喜于自身在设计思路和编程习惯已有了一定的改变,下面我将从度量分析.自身Bug.互测和设计模式四个方向对自己第一章的学习 ...
- 【软件构造】第三章第三节 抽象数据型(ADT)
第三章第三节 抽象数据型(ADT) 3-1节研究了“数据类型”及其特性 ; 3-2节研究了方法和操作的“规约”及其特性:在本节中,我们将数据和操作复合起来,构成ADT,学习ADT的核心特征,以及如何设 ...
- 第一章 Java多线程技能
1.初步了解"进程"."线程"."多线程" 说到多线程,大多都会联系到"进程"和"线程".那么这两者 ...
- 学习笔记(一)--->《Java 8编程官方参考教程(第9版).pdf》:第一章到六章学习笔记
注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.违者本人不负法律责任.违法者自负一切法律责任. ...
- 【黑金原创教程】【Modelsim】【第一章】Modelsim仿真的扫盲文
声明:本文为黑金动力社区(http://www.heijin.org)原创教程,如需转载请注明出处,谢谢! 黑金动力社区2013年原创教程连载计划: http://www.cnblogs.com/al ...
- Pro ASP.NET Core MVC 第6版 第一章
目录 第一章 ASP.NET Core MVC 的前世今生 ASP.NET Core MVC 是一个微软公司开发的Web应用程序开发框架,它结合了MVC架构的高效性和简洁性,敏捷开发的思想和技术和.N ...
随机推荐
- 100114J
经过思考后,很明显,我们可以看出应该是求出两条最长的链,链是指挂在连通块上的
- Java+FlexPaper+swfTools仿百度文库文档在线预览系统设计与实现
笔者最近在给客户开发文档管理系统时,客户要求上传到管理系统的文档(包括ppt,word,excel,txt)只能预览不允许下载.笔者想到了百度文库和豆丁网,百度文库和豆丁网的在线预览都是利用flash ...
- 【HDU 5839】Special Tetrahedron(计算几何)
空间的200个点,求出至少四边相等,且其余两边必须不相邻的四面体的个数. 用map记录距离点i为d的点有几个,这样来优化暴力的四重循环. 别人的做法是枚举两点的中垂面上的点,再把到中点距离相等的点找出 ...
- mac下卸载MySQL
所有跟mysql相关进程都停止掉, 然后终端输入: cd ~/ sudo rm /usr/local/mysqlsudo rm -rf /usr/local/var/mysqlsudo rm -rf ...
- c# 监听文件夹动作
static FileSystemWatcher watcher = new FileSystemWatcher(); /// <summary> /// 初始化监听 ...
- BZOJ 3709: [PA2014]Bohater
3709: [PA2014]Bohater Time Limit: 5 Sec Memory Limit: 128 MBSec Special JudgeSubmit: 1050 Solved: ...
- 【BZOJ-4059】Non-boring sequences 线段树 + 扫描线 (正解暴力)
4059: [Cerc2012]Non-boring sequences Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 440 Solved: 16 ...
- 【bzoj1911】 Apio2010—特别行动队
http://www.lydsy.com/JudgeOnline/problem.php?id=1911 (题目链接) 题意 给出一个序列,将序列分成连续的几段,每段的价值为a*s*s+b*s+c,其 ...
- javascript向上向下取整
alert(Math.ceil(25.9)); alert(Math.ceil(25.5)); alert(Math.ceil(25.1)); alert(Math.round(25.9)); ale ...
- Compiler Theory(编译原理)、词法/语法/AST/中间代码优化在Webshell检测上的应用
catalog . 引论 . 构建一个编译器的相关科学 . 程序设计语言基础 . 一个简单的语法制导翻译器 . 简单表达式的翻译器(源代码示例) . 词法分析 . 生成中间代码 . 词法分析器的实现 ...
递归:在展开阶段里, 这一计算过程构造起一个推迟进行的操作所形成的两条,收缩阶段表现为这些运算的实际执行这种类型的计算过程又一个推迟记性的运算链条刻画,称为一个递归计算过程推迟执行的乘法链条的长度(即位保存其轨迹需要保存的信息量)随n值而线性增长(正比于n) 过程是递归的,说明这个过程的定义中(直接或间接)引用了该过程本身
迭代: 可以用状态变量描述尾递归:在常量空间中执行含有迭代型计算过程的递归过程
练习 1.9inc:将参数增加1dec:将参数减少1(define (+ a b) (if (= a 0) b (inc (+ (dec a) b))))(define (+ a b) (if (= a 0) b (+ (dec a) (inc b))))请用代换模型展示这两个过程在求值(+ 4 5)时所产生的计算过程。区分是迭代还是递归》答案: 第一个调用本身了,是递归过程,计算过程是迭代 