全部学习汇总: ​​https://github.com/GreyZhang/g_SICP​

1193_SICP学习笔记_集合的表达_lisp

    集合的处理有几种典型的操作方式,比如说求解并集、交集、判断元素是否属于结合以及给集合增加元素等。

1193_SICP学习笔记_集合的表达_lisp_02

    这部分设计如果使用python的确是很容易实现的,都是一些已经实现的功能了。几段代码分析都比较容易。

    元素归属判断,一个遍历循环即可。不过这里使用的还是一个递归模式。

    组合,如果是子元素那么原来的集合就是组合的结果。否则,增加一个新的元素。

    交集正好利用前面的子元素的判断功能实现,又是一个遍历处理。但是,这里的实现需要注意的是可能出现空集。

1193_SICP学习笔记_集合的表达_sicp_03

    软件设计自然会考虑效率的问题。

    这里的练习题是一个求解并集的函数设计,思想其实是很简单的。看看是否包含在其中,不包含进行组合即可。这样的设计思想很容易想明白,但是我在lisp使用上遇到一些费解的问题:如果按顺序执行多条语句呢?我翻了这一本书发现似乎没找到。没办法,借用了之前的成果设计了我自己的程序。

1193_SICP学习笔记_集合的表达_二叉树_04

    看起来运行效果还是可以的吧!

    第二个练习感觉上反倒不如第一个难度大,类似python list处理了。

1193_SICP学习笔记_集合的表达_二叉树_05

    有序的集合,在查询上会有很多优势。比如,这种判断是否是子元素的判断其实是可以有条件提前结束而不必走完所有循环的。

1193_SICP学习笔记_集合的表达_数据_06

1193_SICP学习笔记_集合的表达_二叉树_07

    类似的,处理交集的时候其实是可以进行搜索范围的缩小处理的。

1193_SICP学习笔记_集合的表达_数据_08

    这两个练习可以做一下第一个。

1193_SICP学习笔记_集合的表达_sicp_09

    以上是软件实现

1193_SICP学习笔记_集合的表达_二叉树_10

    以上是简单的测试。

1193_SICP学习笔记_集合的表达_二叉树_11

1193_SICP学习笔记_集合的表达_递归_12

    把集合作为二叉树操作,并且在构建树的结构的时候进行左右的大小平衡要求,那么在进行检索的时候效果会增加。按照这样的规则,其实是可以构建出很多不同的二叉树的,但是几乎都是可以提高效率的。自然,如果全都是右边的分支一路扩展,想来还是可以出现与原始状态效率相当的结果的。

1193_SICP学习笔记_集合的表达_lisp_13

    这个实现的确是很巧妙,我还在考虑如何实现这么多的层叠结构,是否是不断的插入组合来实现?看到第一段代码恍然大悟,其实最简单的方式不过是构建新的list。

    查询一个元素是否是集合的元素也容易,如果查到了,必然是节点取值且与之相等。如果空了还没找到那就是没有。如果小于节点数值,结合之前的左小右大规则,即使是有一定在右分支。反之,也是如此。这样,很容易实现一个从属关系的查找。

1193_SICP学习笔记_集合的表达_递归_14

    向集合中合并一个元素:如果树的根节点是这个元素,那么这个组合过程就直接结束了,因为这个元素正好是集合的一个子元素。如果增加的这个元素小于根节点,那么这个这个元素应该是在根节点的左边,接下来的组合应该是根节点、子元素与左分支的组合、右边的分支组合起来的新树。类似的,如果需要加到右边也是类似的处理。

1193_SICP学习笔记_集合的表达_递归_15

1193_SICP学习笔记_集合的表达_sicp_16

1193_SICP学习笔记_集合的表达_sicp_17

    这个问题其实正好跟我前面自己考虑到的一个问题相同了,也就是二叉树一直出现一个分支的情况。基于这个场景,引出了一个新的概念——平衡树。之前还是听说过这个名称的,但是具体的概念我其实是不熟悉的。上面的两个函数都可以把一个tree成list。分析下来,两个效果应该没有差异。这里面的核心差异点在于第二个其实应该属于迭代实现方式而第一个则是递归。如果树比较庞大,第二个的效率应该会好一些。

    第一个实现的主要思想:分别换左右分支,然后借用append向左分支的转换结果增加内容。增加的内容是根节点与右分支转换结果的组合。

    第二个实现的主要思想:直接定义树list的功能,其中list是一个临时存储的缓冲,这样这个list需要选择合理的初始值。之后,还是左右两分支加根节点的组合。因此,其实这两个算法还有一个比较大的差异,一个使用的是append来组合而另一个使用了cons。append其实无形中增加了另一重循环。

1193_SICP学习笔记_集合的表达_数据_18

1193_SICP学习笔记_集合的表达_递归_19

    平衡树的创建,理解起来看起来不难。几个程序的构成元素梳理如下:

    1. 如果左树的元素为0,那么接下来的操作简单,就是(cons '() 全部元素)。

    2. 如果元素数量大于0,那么左树的元素数目left-size为(n - 1)/2的取整,其中-1的目的是为了留一个元素作为树的根节点。

    3. 这样,可以递归求解左树构建的数据。其实,它包含左树和剩余的数据。

    4. 上面的过程做完了,右树的大元素数目可以求解,n - left-size - 1,其中-1也是因为tree的根节点。

    5. 根节点获取,其实是(car 左树剩余数据)。

    6. 递归生成右树数据,最后创建树。

1193_SICP学习笔记_集合的表达_lisp_20

1193_SICP学习笔记_集合的表达_二叉树_21

1193_SICP学习笔记_集合的表达_二叉树_22

    之所以把设计的方法用在集合上,其实是因为结合有数据库的概念。如果是无序的数据,那么检索其实就是一个循环。如果考虑效率,这种数据的组合方式的确是有点“脏”。如果是一个二叉树,尤其是平衡树的这种形式,相关的查询处理效率会好很多。最后的联系跳过了,继续往前推进。

调试过的代码:

(define (union-set set1 set2)

  (append (filter (lambda (x)

                    (not (element-of-set? x set2)))

                  set1)

          set2))

(define (adjoin-set x set)

  (if (null? set)

      (list x)

      (let ((y (car set))

            (remain-set (cdr set)))

        (cond ((= x y)

               set)

              ((> x y)

               (cons y

                     (adjoin-set x remain-set)))

              ((< x y)

                    (cons x set))))))