【Scheme】Scheme 编程学习 (四) —— 递归

news/2025/2/16 5:18:29/

【Scheme】Scheme 编程学习 (四) —— 递归

文章目录

  • 【Scheme】Scheme 编程学习 (四) —— 递归
    • 1. Factorial 阶乘
    • 2. Fibonacci 斐波那契数列
    • 3. Countdown 倒计时
    • 4. Map
    • 5. Recursion "shapes" 递归的形式
      • 5.1 factorial model
      • 5.2 - fibonacci model
      • 5.3 - countdown model
    • 6. Tail call optimisation 尾调用优化
    • 7. Iterative forms 迭代形式
      • 7.1 - my-map model
      • 7.2 - iterative forms - fact
      • 7.3 - Iterative forms - fib

在 Scheme 中函数的通常写法,the normal way to write functions in Scheme,通常会用到递归 (recursion),本节的主要内容为

  • Factorial 阶乘
  • Fibonacci 斐波那契
  • Countdown 倒计时
  • Map
  • Recursion “shapes” 不同形式的递归
  • Tail call optimisation 尾调用优化
  • Iterative forms
  • Discussion

为了更好的理解递归如何运行 (make it easier for understand how recursion works)

1. Factorial 阶乘

(define (fact n)(if (= 0 n)1(* n (fact (- n 1)))))

定义 fact 函数,只接受一个入参 n,当 n 为 0 时结束,其他情况调用 n 乘以 fact(n-1),此函数调用自身,即递归调用,类似的 C语言代码为:

int fact(int n)
{if (0 == n)return 1;elsereturn n * fact(n-1);
}
> (fact 3)
; 6
> (fact 4)
; 24
> (fact 5)
; 120

2. Fibonacci 斐波那契数列

(define (fib n)(if (<= n 2)1(+ (fib (- n 1))(fib (- n 2)))))

此函数也是经典的斐波那契序列,经典的递归编程题,此函数写为 C语言为:

int fib(int n)
{if (n <= 2)return 1;else return fib(n-1) + fib(n-2);
}
> (fib 3)
; 2
> (fib 4)
; 3
> (fib 5)
; 5
> (fib 6)
; 8

3. Countdown 倒计时

(define (countdown n)(if (= n 0)null(begin(display n) ; 打印 n (newline) ; 换行(countdown (- n 1)))))

这是一个比较简单的递归,没太多需要讲解的内容,打印显示,换行,递归调用 入参减一。
begin 用于将后续的三个语句合在一起,类似于 C语言中的大括号,我们都知道 if / else 分支在不写大括号时,最多后跟一个语句。为了使 else 分支可以调用此三个语句,可以写为 C语言

void countdown(int n)
{if (n == 0)return;else{printf("%d", n);printf("\n");countdown(n - 1);}
}
> (countdown 4)
4
3
2
1
() ; null / empty list

4. Map

这次我们再来看一下 map 函数,map 是一个 built-in (内置)函数,这里是一个实现尝试,对表 lst 中的每个元素应用函数 fn,

(define (my-map fn lst)(if (null? lst)null(cons (fn (car lst))(my-map fn (cdr lst)))))

类似的 C语言函数可尝试写为

int abs(int a) { a >= 0 ? return a : return -a; }
int (*f)(int) = abs;void my_map(int (*comp)(int), int arr[], int n)
{if (0 == n)return;else{arr[0] = comp(arr[0]);my_map(comp, arr+1, n-1);}
}
> (my-map abs (list 2 -3 4))
; (2 3 4) 
int arr[] = {2, -3, 4};
int n = sizeof(arr);
my_map(f, arr, n);

5. Recursion “shapes” 递归的形式

5.1 factorial model

(define (fact n)(if (= 0 n)1(* n (fact (- n 1))))

让我们尝试一下 (fact 3) 看看内部发生了什么

(fact 3) ; 当我们调用 (fact 3) 时 内部为:
(* 3 (fact 2)); 接下来我们展开 (fact 2)
(* 3 (* 2 (fact 1))) ; 接着展开 (fact 1)
(* 3 (* 2 (* 1 (fact 0))));展开 (fact 0) 为 1
(* 3 (* 2 (* 1 1))); 先计算 (* 1 1) 结果为 1
(* 3 (* 2 1)); 再计算 (* 2 1) 结果为 2
(* 3 2); 计算 (* 3 2) 结果为 6
6

5.2 - fibonacci model

(define (fib n)(if (<= n 2)1(+ (fib (- n 1))(fib (- n 2)))))

调用斐波那契函数并设置 n 为 5

(fib 5)
(+ (fib 4) (fib 3)); 展开为 (fib 4) 和 (fib 3) 的和,再展开 (fib 4) 和 (fib 3)
(+ (+ (fib 3) (fib 2)) (+ (fib 2) (fib 1))); 为 (fib 3) (fib 2) 的和 与 (fib 2) (fib 1) 的和之和 
(+ (+ (+ (fib 2) (fib 1)) 1) (+ 1 1)); 展开 (fib 3) ,由于(fib 2) (fib 1) 结果为 1,替换
(+ (+ (+ 1 1) 1) 2); 全部转为具体的数字计算
(+ (+ 2 1) 2)
(+ 3 2)
5; 累加结果为 5

此种递归展开结构类似三角形

5.3 - countdown model

(define (countdown n)(if (= n 0)null(begin (display n) (newline)(countdown (- n 1)))))
(countdown 4)
(countdown 3)
(countdown 2)
(countdown 1)
(countdown 0)
()

it’s a kind of uni-function,执行时 countdown 得到的形式类似单一函数

6. Tail call optimisation 尾调用优化

一般语言调用递归时 每次都会产生一个栈帧 (stack frame),在 scheme 中

  • The previous stack frame is no longer needed 前一个栈帧不再需要,当我们计算 (countdown 3) 时,我们不需要前一个栈帧 (countdown 4) 的任何信息
  • Throw it away 这时我们就可以将其丢弃掉

7. Iterative forms 迭代形式

7.1 - my-map model

(define (my-map fn lst)(if (null? lst)null(cons (fn (car lst))(my-map fn (cdr lst)))))
(my-map abs (list 2 -3 4)); 调用 my-map 展开 会创建一个点对,car 为对列表第一个元素应用 abs  
(cons (abs 2) (my-map abs (list -3 4))); 继续对剩余更少的元素进行 my-map
(cons 2 (cons (abs -3) (my-map abs (list 4)))); (abs 2) 的结果为 2
(cons 2 (cons 3 (cons (abs 4) (my-map abs null)))); (abs -3) 的结果为 3
(cons 2 (cons 3 (cons 4 null))); (my-map abs null) 会返回 null 
(cons 2 (cons 3 (list 4))); 根据之前的章节,这里会产生一个 list 
(cons 2 (list 3 4)); 继续合并为 (list 3 4)
(list 2 3 4)

the “shape” of my-map is again a kind of triangular form,my-map 的展开形式仍然是一种三角形格式

7.2 - iterative forms - fact

让我们尝试以不同的方式实现 fact ,为了避免这种三角形形式

(define (fact-it n)(define (impl acc count)(if (= 0 count)acc(impl (* count acc) (- count 1))))(impl 1 n))

acc 这里的意思为 accumulate 累计,上述代码中为累乘的结果,初值为 1 ,依次累乘 n,n-1,… 1
类似的 C++ 代码 (untested) 可以写作:

int fact_it(int n)
{auto impl = [](int acc, int count)-> int{ if (0 == count) return acc;else return impl(count*acc, count-1);};return imp(1,n);
}
> (fact-it 3)
; 6
> (fact-it 4)
; 24
> (fact-it 5)
; 120

让我们看一下它的形式 (Shape)

(fact-it 3)
(impl 1 3)
(impl (* 3 1) (- 3 1))
(impl 3 2)
(impl (* 2 3) (- 2 1))
(impl 6 1)
(impl (* 1 6) (- 1 1))
(impl 6 0); 此时 count 为 0 ,返回 acc 即 6
6

7.3 - Iterative forms - fib

让我们看一下如何以同样的方式实现 fib

(define (fib-it n)(define (impl acc1 acc2 count)(if (= count 2)acc1(impl (+ acc1 acc2) acc1 (- count 1))))(impl 1 1 n)) ; 函数实际的函数体

acc1 acc2 为两个累加

> (fib-it 2)
; 1
> (fib-it 3)
; 2
> (fib-it 4)
; 3
> (fib-it 5)
; 5
(fib-it 5)
(impl (+ 1 1) 1 (- 5 1))
(impl 2 1 4)
(impl (+ 2 1) 2 (- 4 1))
(impl 3 2 3)
(impl (+ 3 2) 3 (- 3 1))
(impl 5 3 2); 当 n 为 2 时返回 acc1 即 5
5

此种形式的展开均为独立的计算,并不基于前一次的计算结果

我们可以以同样的方式优化 map 吗?

  • No, but “Tail call optimisation modulo cons” 不行,但是尾调用优化以 cons 为模块

原视频地址: https://www.bilibili.com/video/BV1Kt411R7Wf?p=4&spm_id_from=pageDriver


http://www.ppmy.cn/news/1013402.html

相关文章

代码随想录算法训练营第五十八天| 739. 每日温度 496.下一个更大元素 I

代码随想录算法训练营第五十八天| 739. 每日温度 496.下一个更大元素 I 什么情况下使用单调栈&#xff1a;通常是一维数组&#xff0c;要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置&#xff0c;此时我们就要想到可以用单调栈了。时间复杂度为O(n)。 一、…

Navicat连接SQL Server报错:IM002 未发现数据源名称且未指定驱动

Navicat Premium连接SQL Server软件时&#xff1a;报IM002错误&#xff0c;未发现数据源名称且未指定驱动程&#xff1a; 解决办法&#xff1a;查找Navicat Premium的安装目录D:\Navicat Premium\&#xff0c;你会找到一个文件sqlncli_x64.msi&#xff08;D:\Navicat Premium\s…

C#声明一个带返回值的委托

1、声明 public delegate string TestDel(string str); 2、使用 TestDel t; t (string str) > str; t (string str) > str "1"; t (string str) > str "2"; t (string str) > str "3"; Console.WriteLine(t ("hhhh&qu…

122、SpringBoot中有几种定义Bean的方式?

SpringBoot中有几种定义Bean的方式? SpringBoot中有几种定义Bean的方式?代码栗子演示1、@Bean2. @Component3. @Controller、@RestController、@Service、@Repository4. @ControllerAdvice、@RestControllerAdvice5. @Configuration6. @Import7. BeanDefinition8. \<bean\…

Python路径拼接

在Windows系统中&#xff0c;路径分隔符是反斜杠 \&#xff0c; 而在Linux系统中&#xff0c;路径分隔符是正斜杠 /。 为了在多平台上保持路径正确&#xff0c;应该使用os.path.join()函数来拼接路径&#xff0c;这样会根据当前系统的路径分隔符来自动调整。 BASE_DIRS os.pa…

[CKA]考试之Sidecar代理

由于最新的CKA考试改版&#xff0c;不允许存储书签&#xff0c;本博客致力怎么一步步从官网把答案找到&#xff0c;如何修改把题做对&#xff0c;下面开始我们的 CKA之旅 题目为&#xff1a; Context 将一个现有的 Pod 集成到 Kubernetes 的内置日志记录体系结构中&#xff…

snake_c

如何开始界面&#xff0c;点击后进入游戏界面&#xff1f; void Welcome(){printf("\n\n\t欢迎来到贪吃蛇的世界\n");printf("\t\t\t----by adair\n\n\n");printf("\t按下空格开始游戏\n");system("pause"); //暂停屏幕&#xff0c;…

C#控制台程序+Window增加右键菜单

有时候我们可能会想定制一些自己的右键菜单功能&#xff0c;帮我们减少重复的操作。那么使用控制台程序加自定义右键菜单&#xff0c;就可以很好地满足我们的需求。 1 编写控制台程序 因为我只用到了在文件夹中空白处的右键菜单&#xff0c;所以这里提供了一个对应的模板&…