SCIP2.41

来源:互联网 发布:梦龙软件下载 编辑:程序博客网 时间:2024/04/28 23:49

SICP 2.41


  • SICP 241
    • 题目叙述
    • 思路分析
      • 引子
      • 题目解答
    • 结语

题目叙述

读写出一个过程,他能产生出所有小于等于给定整数你的正的相异整数i、j和k的有序三元组,使每个三元组的三个元之和的等于给定的整数s。

思路分析

引子

给定一个序列没希望生成这种序列的所有的排列形式,类似于全排列。例如'(1 2 3)生成的排列形式如下:            (1 2 3)            (1 3 2)            (2 1 3)            (2 3 1)            (3 1 2)            (3 2 1)

思路:运用了嵌套映射与递归的思想。所谓嵌套映射其实就是将枚举器枚举出来的列表元素以某种”规则”进行逐个进行所谓的”扩充”,例如枚举列表’(1 2 3),扩充后的列表为’((1 4) (2 5) (3 6))。这里的列表所用到的扩充规则就是对于枚举表中的每个元素与其相差3的数字组成序对,那么就出现了扩充后的列表。 递归的思想就不用多说了。


那么引子中的题目是如何组合这两种思想的呢?
路线:递归->取一个元素,将其与其后已经排列后的序列进行”粘合”,如’(1 2 3),取1,那么’(2 3)的排列为’(2 3),’(3 2),然后将1与他们进行粘合,那么结果为’(1 2 3), ‘(1 3 2),逐次取元素进行粘合,那么就会获得所有的全排列。
嵌套->在递归中已经有所提及,就是映射的嵌套,对于本例来说一个具体的理解就是’((() () ()) (() () ()) (() () ())),最外层的映射指的就是’(() () ()),其中的内层映射最外层映射中的一个’()被映射成’( () () ()), 举例来说对于’(1 2 3)最终被映射成为’(‘((1 2 3) (1 3 2)) ‘((2 1 3) (2 3 1)) ‘((3 1 2) (3 2 1))),先从内层映射说起,如’((1 2 3) (1 3 2))就是其中的一个内层映射,而多个内层映射组成起来那么就成为外层映射,成为最终形式。(我发现好难说清楚啊)
代码如下:

(define (premutations s) (if (null? s)       (list nil)       (flatmap (lambda (x)          (map (lambda (p) (cons x p))              (permutations (remove x s))))              s)))

这是SCIP中的源码,个中体会看看书就知道了。

题目解答

解题的思路是因序对的概念引起的,源于二元序对变成三元序对的的思路,三元序对的组成基于二元序对实现。基于这个想法,我依葫芦画个瓢,我认为如果要求三元之和等于给定的整数,那么我应该先求两元之和等于给定的整数,然后在这个基础上实现求三元之和给定的整数,毕竟可以用递归了吗。
下面的代码是二元之和的代码:

;;;;我这个函数中的s代表序列,与题中的不一样,n代表三元之和(define (two-sum-pair s n) (filter (lambda (x) (not (null? x))) (map (lambda (x)                    (if (and                          (have? (remove x s) (- n x))                         (or (< x (- n x))))                          (list x (- n x))                         '())) s)))

这是测试结果

代码解析:主要分两部分,第一部分就是映射,第二部分是过滤器。
映射:对于序列s来说,如果如果n-x的整数存在于非x的序列中,那么我们就把他们组合在一起。如对于第一个测试,s=’(1 2 3 4 5), n = 6, 对于1来说n-x=5存在于非x的序列(2 3 4 5),所以组合1与5,(1 5)。另外为了防止重复比如(5 1),所以设置了condition (< x (- n x))。
过滤器:过滤器主要是为了过滤掉空序对的,应为如果没有过滤的话,有些数无法组成满足条件的序对,所以为空序对。如果去掉序对,结果如下图所示:
测试结果


上一部分就是求二元序对的等于给定的整数,下面基于此求三元序对等于给定的整数


下面是代码:

;;;求三元序对,s代表序列,n代表三元之和(define (three-sum-pair s n)  (flatmap (lambda (x)          (map (lambda (y)                (cons x y))          (two-sum-pair (remove-interval x s) (- n x))))      s))

代码解析:主要有两部分组成,一个是嵌套映射部分,另外一个是累积部分。
嵌套映射部分
完成了将三元求和分解成两元求和,因为两元求和已经写出,所以直接拿来用。
累积部分
为了让内映射之间能够组合成在一个列表中,如‘(((1 2 3) (2 3 4)) ‘((2 3 6) (3 4 5)))变成’((1 2 3) (2 3 4) (2 3 6) (3 4 5)), 就是变成类似于这种形式。
下面是测试结果:
为了让测试方便,为代码又写了个壳,

;;;求三元序列,这里的n代表要生成的序列个数1-n,如3,生成’(1 2 3);s代表给定的整数(define (three-sum n s)  (three-sum-pair (enumerate-interval 1 n) s))

测试结果

上述部分的代码都是部分代码,所以其中的有些小函数没有 没有介绍,故贴上所有部分的代码,以供参考,此外,一些枚举器,累加器,映射,这些SICP上面都有,我就不贴了,自行查阅,具体 代码如下:

;;;查看此函数中是否有此项,如‘(1 2 3)含有1返回#t, 不含有5返回#f(define (have? sequence item)  (cond ((null? sequence) #f)        ((= (car sequence) item) #t)        (else (have? (cdr sequence) item))));;;;我这个函数中的s代表序列,与题中的不一样,n代表三元之和(define (two-sum-pair s n) (filter (lambda (x) (not (null? x))) (map (lambda (x)                    (if (and                          (have? (remove x s) (- n x))                         (or (< x (- n x))))                          (list x (- n x))                         '())) s)))(define (two-sum-pair s n) (map (lambda (x)                    (if (and                          (have? (remove x s) (- n x))                         (or (< x (- n x))))                          (list x (- n x))                         '())) s));;;;;删除比item小的整个区间,如(remove-interval 3 ‘(1 2 3 4))= '(4)(define (remove-interval item seq)   (filter (lambda (x) (> x item))          seq));;;求三元序对,s代表序列,n代表三元之和(define (three-sum-pair s n)  (flatmap (lambda (x)          (map (lambda (y)                (cons x y))          (two-sum-pair (remove-interval x s) (- n x))))      s));;;求三元序列,这里的n代表要生成的序列个数,如3,生成’(1 2 3);s代表给定的整数(define (three-sum n s)  (three-sum-pair (enumerate-interval 1 n) s))

结语

不知不觉就啰嗦了这么多,这个问题想了一晚上,算是想通了,所以为了记录下自己的成果,以后也可参阅,发现SICP的书真是思维性好强,有时题目要看好久才能解出,继续努力。
2015.4.23 南一楼443

2 0
原创粉丝点击