SICP学习笔记(2.3.3)

来源:互联网 发布:数据库的储存特点 编辑:程序博客网 时间:2024/06/06 03:14

                                           SICP学习笔记(2.3.3)
                                                  周银辉

 

1,集合作为未排序的表

这基本上相当于在使用for或foreach语句完成所有的操作,效率自然不高。下面是采用这种方式实现的集合:

;=========集合作为未排序的表=======

(define (element-of-set? x set)
  (cond ((null? set) #f)
        ((equal? x (car set)) #t)
        (else (element-of-set? x (cdr set)))))

(define (adjoin-set x set)
  (if (element-of-set? x set)
      set
      (cons x set)))

(define (intersection-set set1 set2)
  (cond ((or (null? set1) (null? set2)) '())
        ((element-of-set? (car set1) set2)
         (cons (car set1) (intersection-set (cdr set1) set2)))
        (else (intersection-set (cdr set1) set2))))

(define (union-set set1 set2)
  (cond ((null? set1) set2)
        ((not (element-of-set? (car set1) set2))
         (cons (car set1) (union-set (cdr set1) set2)))
        (else (union-set (cdr set1) set2))))
        
;test
(define mySet1 '(1 3 4 2 5 7))
(define mySet2 '(1 5 8 6))
(element-of-set? 8 mySet1)
(adjoin-set 8 mySet1)
(intersection-set mySet1 mySet2)
(union-set mySet1 mySet2)

 

 我们注意到,element-of-set?将对集合做线性遍历,所以其时间复杂度为O(n),那么自然地,adjoin-set 也为O(n),而intersection-set与union-set对于set1中的每一个元素都将检查其是否在set2中,那么其计算步数大概是n*m, 其中,n和m假设为set1和set2的元素个数,所以其时间复杂度为O(n^2)

 

 

2,集合作为未排序的表,但集合为“多重集”

 

所谓多重集,就是集合中的元素可以重复,重复的最大次数称为集合的“重数”,明显地,我们向多重集中添加元素以及求集合的并集的时候是不需要检查元素是否已经存在的,少了检查也就少了遍历,这会提高效率。(但求交集时仍是需要检查的),下面是代码:

;=========集合作为未排序的表(多重集)=======
;about Multiset : http://en.wikipedia.org/wiki/Multiset

(define (element-of-set? x set)
  (cond ((null? set) #f)
        ((equal? x (car set)) #t)
        (else (element-of-set? x (cdr set)))))

(define (adjoin-set x set)
  (cons x set))

(define (intersection-set set1 set2)
  (cond ((or (null? set1) (null? set2)) '())
        ((element-of-set? (car set1) set2)
         (cons (car set1) (intersection-set (cdr set1) set2)))
        (else (intersection-set (cdr set1) set2))))

(define (union-set set1 set2)
  (cond ((null? set1) set2)       
        (else (cons (car set1) (union-set (cdr set1) set2)))))

;test
(define mySet1 '(1 3 1 2 3 7))
(define mySet2 '(1 5 5 6))
(element-of-set? 2 mySet1)
(adjoin-set 2 mySet1)
(intersection-set mySet1 mySet2)
(union-set mySet1 mySet2)

 

 

 

3,集合作为排序的表(升序)

 

排序的好处是可以消除不必要的遍历,比如检查一个元素是否在集合中,其比该集合的第一个元素都小,那么它肯定不在几何中,没必要再和集合的其他元素比较了。下面是代码:

;=========集合作为排序的表(升序)=======

(define (element-of-set? x set)
  (cond ((or (null? set) (< x (car set) )) #f)
        ((equal? x (car set)) #t)
        (else (element-of-set? x (cdr set)))))

(define (adjoin-set x set)
  (cond ((< x (car set)) (cons x set))
        ((> x (car set)) (cons (car set) (adjoin-set x (cdr set))))
        (else set)))

(define (intersection-set set1 set2)
  (if (or (null? set1) (null? set2))
      '()   
      (let ((x1 (car set1)) (x2 (car set2)))
        (cond ((= x1 x2)
               (cons x1
                     (intersection-set (cdr set1)
                                       (cdr set2))))
              ((< x1 x2)
               (intersection-set (cdr set1) set2))
              ((< x2 x1)
               (intersection-set set1 (cdr set2)))))))

(define (union-set set1 set2)
  (cond ((null? set1) set2)
        ((null? set2) set1)
        (else
         (let ((x1 (car set1)) (x2 (car set2)))
           (cond ((= x1 x2)
                  (cons x1
                        (union-set (cdr set1) (cdr set2))))
                 ((< x1 x2)
                  (cons x1
                        (union-set (cdr set1) set2)))
                 ((< x2 x1)
                  (cons x2
                        (union-set (cdr set2) set1))))))))

 

;test
(define mySet1 '(1 3 5 7 9))
(define mySet2 '(1 2 4 6 8))
(element-of-set? 9 mySet1)
(adjoin-set 8 mySet1)
(adjoin-set 5 mySet1)
(intersection-set mySet1 mySet2)
(union-set mySet1 mySet2)

 简单解释一下:
对于element-of-set?,如果集合为空或者待检测元素x比集合的第一个元素都小,那么x肯定不再集合中;如果x与集合的第一个元素相等,那么x肯定在集合中(废话~~);否则,就检测x是否在(cdr set)中,即检测其是否在除去第一个元素后的子集中。这虽然也是一个线性遍历,复杂度为O(n),当相比于不排序而言,其不必遍历完集合的所有元素,所以工作量会小很多。说得更直白一点就是:对于未排序的集合,当你遍历该集合时,如果在某一时刻未找到一个元素,你只能说“到目前为止还没找到”,并不代表将来找不到,所以你只有遍历完整个集合后才能下结论说“的确没有”,而排序后的集合,当你达到某个上限时,你便可以断定“即便找下去也不会有的”,所以干脆不找了。

对于adjoin-set,向集合追加元素x时,如果元素x比集合的第一个元素小,那么直接将x放置到集合的最前面;如果x比集合的第一个元素大,那么其应该追加到(cdr set)这个子集中(别搞丢了第一个元素);如果x和集合的第一个元素相等,那么不追加,直接将集合返回。

对于intersection-set,求集合set1和集合set2的交集,如果set1为空或set2为空,那么交集为空;设set1与set2的第一个元素分别为x1和x2,如果x1等于x2,那么将x1或者x2纳为交集元素,然后继续求(cdr set1)和(cdr set2)的交集;如果x1小于x2,那么很明显x1被淘汰了,所以继续拿(cdr set1)和set2做交集,同理,如果x2小于x1,那么x2被淘汰。

对于union-set,求集合set1和集合set2的并集,原理和求交集差不多,只不过不是较小的值被淘汰,而是刚好相反,较小值被纳如并集中。

很明显,上面的求交集和并集的操作的计算步骤大概是(m+n),其中m,n是set1和set2的长度,所以这两个操作的时间复杂度是O(n)

 

 

 4, 集合作为二叉树

 

其实不能一概而论地说这种方式好还是不好,因为这取决于具体的代码实现,因为在遍历二叉树的时候很可能一不小心就写出了一个“双递归”算法,导致遍历速度超慢,但毕竟SICP上提到的二叉树是“平衡二叉树”,是经过“精心排序”的,利用得好的话,则效率颇高,先贴代码,再解释:

;=========集合作为二叉树=======

(define (entry tree) (car tree))

(define (left-branch tree) (cadr tree))

(define (right-branch tree) (caddr tree))

(define (make-tree entry left right)
  (list entry left right))

(define (element-of-tree? x set)
  (cond ((null? set) #f)
        ((= x (entry set)) #t)
        ((< x (entry set))
         (element-of-tree? x (left-branch set)))
        (else
         (element-of-tree? x (rigth-branch set)))))

(define (adjoin-tree x set)
  (cond ((null? set)
         (make-tree x '() '()))
        ((= x (entry set))
         set)
        ((< x (entry set))
         (make-tree (entry set)
                    (adjoin-tree x (left-branch set))
                    (right-branch set)))
        (else
         (make-tree (entry set)
                    (left-branch set)
                    (adjoin-tree x (right-branch set))))))

 

;intersection-set 来自于“集合作为排序的表(升序)”

(define (intersection-set set1 set2)
  (if (or (null? set1) (null? set2))
      '()   
      (let ((x1 (car set1)) (x2 (car set2)))
        (cond ((= x1 x2)
               (cons x1
                     (intersection-set (cdr set1)
                                       (cdr set2))))
              ((< x1 x2)
               (intersection-set (cdr set1) set2))
              ((< x2 x1)
               (intersection-set set1 (cdr set2)))))))

 

; union-set 来自于“集合作为排序的表(升序)”

(define (union-set set1 set2)
  (cond ((null? set1) set2)
        ((null? set2) set1)
        (else
         (let ((x1 (car set1)) (x2 (car set2)))
           (cond ((= x1 x2)
                  (cons x1
                        (union-set (cdr set1) (cdr set2))))
                 ((< x1 x2)
                  (cons x1
                        (union-set (cdr set1) set2)))
                 ((< x2 x1)
                  (cons x2
                        (union-set (cdr set2) set1))))))))

(define (union-tree tree1 tree2)
  (list->tree (union-set (tree->list-2 tree1) (tree->list-2 tree2))))

(define (intersection-tree tree1 tree2)
  (list->tree (intersection-set (tree->list-2 tree1) (tree->list-2 tree2))))

 

;这个算法是一个“树形递归”
(define (tree->list-1 tree)
  (if (null? tree)
      '()
      (append (tree->list-1 (left-branch tree))
              (cons (entry tree)
                    (tree->list-1 (right-branch tree))))))

;这个算法是一个“尾递归”,也就是说其是“迭代的”
(define (tree->list-2 tree)
  (define (copy-to-list tree result-list)
    (if (null? tree)
        result-list
        (copy-to-list (left-branch tree)
                      (cons (entry tree)
                            (copy-to-list (right-branch tree)
                                          result-list)))))
  (copy-to-list tree '()))

 

(define (list->tree elements)
  (car (partial-tree elements (length elements))))

 

(define (partial-tree elts n)
  (if (= n 0)
      (cons '() elts)
      (let ((left-size (quotient (- n 1) 2)))
        (let ((left-result (partial-tree elts left-size)))
          (let ((left-tree (car left-result))
                (non-left-elts (cdr left-result))
                (right-size (- n (+ left-size 1))))
            (let ((this-entry (car non-left-elts))
                  (right-result (partial-tree (cdr non-left-elts)
                                              right-size)))
              (let ((right-tree (car right-result))
                    (remaining-elts (cdr right-result)))
                (cons (make-tree this-entry left-tree right-tree)
                      remaining-elts))))))))

;-------------------test----------------------------
(define myTree1
  (make-tree 7
             (make-tree 3
                        (make-tree 1 '() '())
                        (make-tree 5 '() '()))
             (make-tree 9
                        '()
                        (make-tree 11 '() '()))))
(define myTree2
  (make-tree 3
             (make-tree 1 '() '())
             (make-tree 7
                        (make-tree 5 '() '())
                        (make-tree 9
                                   '()
                                   (make-tree 11 '() '())))))

(define myTree3
  (make-tree 5
             (make-tree 3
                        (make-tree 1 '() '())
                        '())  
             (make-tree 9
                        (make-tree 7 '() '())
                        (make-tree 11 '() '()))))

(tree->list-1 myTree1)
(tree->list-2 myTree1)

(tree->list-1 myTree2)
(tree->list-2 myTree2)

(tree->list-1 myTree3)
(tree->list-2 myTree3)

(list->tree '(1 3 5 7 9 11))
(list->tree '(1 3 5 7 8))

(define myTree4 (list->tree '(1 3 5 7 9 11)))
(define myTree5 (list->tree '(1 3 5 7 8)))

(tree->list-2 (intersection-tree myTree4 myTree5))
(union-tree myTree4 myTree5)

 

element-of-tree?函数和adjoin-tree函数与“集合作为排序的表(升序)”的算法是一样的,因为这里的平衡二叉树,实际上也是排序的,求并集和交际则是将平衡二叉树转化成排序的表,然后利用排序表来计算。
tree->list-1和tree->list-2的不同之处在于前者是一个双递归,后者是尾递归(迭代的),所以两者的效率不在一个数量级上,关于这个嘛,在SICP的1.2.2节讲“树形递归”的时候就已经说过了。

5,练习2.59~2.65

答案上文找

 

 

6,练习2.66

 

这个和 element-of-tree?函数的算法几乎是一样的:

(define (lookup given-key set-of-records)
  (if (null? set-of-records)
      false
      (let ((r (entry set-of-records)))
        (let ((k (key r)))
          (cond ((= given-key k) r)
                ((< given-key k)
                 (lookup given-key (left-branch set-of-records)))
                ((> given-key k)
                 (lookup given-key (right-branch set-of-records))))))))

 

 

注:这是一篇读书笔记,所以其中的内容仅 属个人理解而不代表SICP的观点,并随着理解的深入其中 的内容可能会被修改

 

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 在淘宝上买东西付款不发货怎么办 买家拍下商品卖家拒绝发货怎么办 没有中文标签被投诉到工商局怎么办 淘宝申请退款卖家强制发货怎么办 1688卖家交易不小心关闭怎么办 拼多多两天不发货怎么办自动退款吗 在家里放的东西找不到了怎么办 在家里烧东西烟太大了怎么办 转转上买的二手东西有问题怎么办? 大巴车过虎门大桥错走小车道怎么办 pe高压膜中间松两边紧怎么办? 穿着浅口的高跟鞋走路老是掉怎么办 五吧同城模特兼职被骗了怎么办 华为手机充电玩黑屏怎么办教学视频 衣服肩膀被衣架撑变形了怎么办 货已经安到客户家客户不付款怎么办 微商卖家不给退货不给换货怎么办啊 淘宝卖家已签收退货却不退款怎么办 申请换货卖家收到货不给换怎么办 我买了个机器想退货怎么办 卖家要求退回去又拒收怎么办 拼多多48小时还不发货怎么办 退货退款单号填错了退不了款怎么办 淘宝退货退款快递单号填写错怎么办 蘑菇街退货忘记填快递单号了怎么办 您尝试购买的项目已停止供货怎么办 oppo新手机搬家搬了一半怎么办 开手机店手机卖不出去怎么办 京东买的东西误按签收了怎么办 小米5x充电宝充电自动断电怎么办 小米2s开不了机怎么办充电闪红灯 寄快递写错地址但已经发货了怎么办 拼多多发货时快递公司写错了怎么办 千牛发货信息写错了怎么办 发货物流单电话写错了怎么办 顺丰寄电脑保价后电脑进水了怎么办 微销通分享小程序没有二维码怎么办 京东的东西退掉但是赠品怎么办 买手机7天不给退换怎么办 买的水果拒收了商家不退钱怎么办 京东第三方签收后退货怎么办