2.2.2 层次结构

2.2.2 层次结构
序列的表示是用列表的方式,序列的元素本身也能是序列。例如,我们能够把
对象((1 2) 3 4) 以如下的方式组装起来。

(cons (list 1 2) (list 3 4))

作为有三个元素的列表,第一个元素是一个列表(1,2).的确,用解释器打印出结果,是
有建设性的。图2。5是以数对的形式表示了这个结构。

                                         (3 4 )
                                           |
                                           !

((1 2 ) 3 4 ) ---> | @ | @ | ----------->| @ | @ |----->| @ | /|

                     |                     |              |
                     !                     !              !
(1 2 )  ---------> | @ | @ |->| @ | /|   | 3 |          | 4 |
                     |          |
                     !          !
                   | 1 |      | 2 |

图2.5 (cons (list 1 2) (list 3 4))形成的结构

对序列的元素仍是序列的另一种看法是树。序列的元素是树的分支,元素也就是它们本身的序列是子树。
图2.6显示了图2.5中的结构的树形。

                     ((1 2) 3 4)
                      /   |   \
                     /    |    \
                    /     |     \
                 (1 2)  3     4
                   / \
                  /   \
                 1     2
图2.6 把图2.5中的列表结构显示为一棵树。

处理树形结构,递归是一种常用的工具,因为我们能够经常归纳树形操作,
到子树上的操作,再归纳到子树的子树等等。直到达到了树的叶子。作为一个例子,
比较2.2.1部分中的length程序和count-leaves程序,这个程序返回树的叶子的数量。

(define x (cons (list 1 2) (list 3 4)))

(length x)
3
(count-leaves x)
4

(list x x)
(((1 2) 3 4) ((1 2) 3 4))

(length (list x x))
2

(count-leaves (list x x))
8

为了实现count-leaves程序,重调用为了计算长度的递归计划。

  一个列表的长度等于1加上列表的cdr的长度。
  空列表的长度为0

count-leaves程序是相似的,对于空列表的值是相同的,也是0
   *  空列表的叶子数为0
但是在递归的步骤中,当我们抽取出列表的car部分时,我们必须
认真对待car部分本身是一棵树,这种树的叶子需要我们计算的情况。
所以适合的递归步骤如下:
   *  一棵树的叶子数等于树的car部分的叶子数加上树的cdr部分的叶子数
最后,通过cars,我们到达了实际的叶子结点,我们另一个基本的事实
   *  一个叶子结点的叶子数为1。

为了辅助写针对树形结构的递归程序,Scheme提供了一个原生的判断式pair?,
它测试它的参数是否是一个数对,这是完整的程序如下:

(define (count-leaves x)
     (cond ((null? x) 0)
           ((not (pair? x)) 1)
           (else (+ (count-leaves (car x))
                    (count-leaves (cdr x))))
     )
)

练习2.24
  假定我们评估表达式(list 1 (list 2 (list 3 4))).给出解释器的打印结果。
  相应的盒子与指针的结构,作为树的解释(正如在图2.6)

练习2.25
   从下面的表达式中取出7,给出cars和cdrs的组合。
   (1 3 (5 7) 9)
   ((7))
   (1 (2 (3 (4 (5 (6 7)))))) 

练习2.26
   假定我们定义 x和y是两个列表
   (define x (list 1 2 3))
   (define y (list 4 5 6))
 
   解释器在评估以下的表达式时,返回的相应的打印结果是什么?
 
  (append x y)
 
  (cons x y)
 
  (list x y)

练习2.27
  修改你的在练习2.18中的reverse程序,生成一个deep-reverse程序,
  这个接受一个列表为参数,返回一个列表作为它的结果。它的元素是倒序的。
  所有的子列表也是深层次的倒序的。例如
 
  (define x (list (list 1 2) (list 3 4)))

   x
   ((1 2) (3 4))

   (reverse x)
   ((3 4) (1 2))

   (deep-reverse x)
   ((4 3) (2 1))


练习2.28
   写一个程序fringe,实现的是,以一个树作为参数,树被表示为一个列表,返回
   一个列表它的所有的元素是树的所有的叶子,以左到右的顺序的排列。例如:
 
   (define x (list (list 1 2) (list 3 4)))
 
    (fringe x)
    (1 2 3 4)

    (fringe (list x x))
    (1 2 3 4 1 2 3 4)


练习2.29
    一个二叉树由两个分支组成。一个是左分枝,另一个是右分支。
任何一个分支有一个定长的内容,它或者是一个权重值,或者是另一个
二叉树。我们能表示一个二叉树,使用复合的数据结构。通过使用两个分支组装它。
例如使用列表。

  (define (make-mobile left right)
    (list left right))
  
一个分支由一个长度值和一个结构组装而成。这个结构可能是一个简单的权值或者是另一个
树。
  
   (define (make-branch length structure)
   (list length structure))

任务如下;
a. 写相应的选择子left-branch ,right-branch, 它们返回一个二叉树的分支。
   branch-length branch-structure 它们返回一个分支的组件。
 
b. 使用你的选择子,定义一个程序total-weight返回一个二叉树的总权值。

c. 如果一个二叉树的顶级左分叉的力矩等于顶级右分支的力矩,并且子二叉树也是平衡的,那么
这个二叉树是平衡的。 力矩的含义(左子树的长度乘以权值 或者是 右子树的长度乘以权值)
设计一个判断式来测试一个二叉树是否是平衡的。

d.假设我们改变了二叉树的表示,组装子写成了如下的内容。
 
  (define (make-mobile left right)
    (cons left right))
  
  (define (make-branch length structure)
   (cons length structure))
为了转到新的表示方法上,你需要修改你的程序有多少呢?

*在树上的映射操作

正如map对于处理序列是一个强有力的抽象,map结合递归在处理树时,也是一个强有力的抽象。
例如,scale-tree程序与2.2.1部分中的scale-list是相似的,参数是一个数据因子,
和一个叶子结点是数据的树。它返回一个相同的结构的树,它的叶子上的数据都乘以一个相同的因子。
对于程序scale-tree的递归计划与 count-leaves的内容相似。

(define (scale-tree tree factor)
   (cond  ((null? tree) null)
          ((not (pair? tree)) (* tree factor))
          (else (cons (scale-tree (car tree) factor) (scale (cdr tree) factor)))
   )
)

(scale-tree (list 1 (list 2 (list 3 4) 5) (list 6 7)) 10)
(10 (20 (30 40) 50) (60 70))

实现scale-tree的另一种方式是把树看作是子树的序列,然后使用map.
我们让map序列,缩放任何一个子树,返回一个结构的列表。
在基本的案例是,树是一个叶子,我们简单地乘以一个因子。

(define (scale-tree tree factor)
   (map (lambda (sub-tree)
           (if (pair? sub-tree)
               (scale-tree sub-tree factor)
               (* sub-tree factor)
           ) 
         ) tree
   )
)

许多的树的操作能用与序列操作和递归相似的结合来实现。

练习2.30
定义一个square-tree程序与练习2.21的square-list相似。
这个square-tree应用有如下的行为:

(square-tree    (list 1 (list 2 (list 3 4) 5) (list 6 7)))
(1 (4 (9 16) 25) (36 49))

定义square-tree 直接定义不用任何高层次程序 或者使用map和递归。

练习2.31
抽象你的练习2.30的答案以产生程序tree-map,使得square-tree可以被定义成如下的样子。
(define (square-tree tree) (tree-map square tree))


练习2.32
 我们能表示一个集合作为一个元素不重复的列表,我们也能表示集合的所有的子集合组成的集合
作为列表的列表。例如 如果集合是(1 2 3),那么所有的子集合的集合是(() (1) (2) (3) (1 2)
(1 3) (2 3) (1 2 3)). 完成一个程序的如下的定义,生成一个集合的所有的子集组成的集合。
再给出一个清晰的解释为什么它能工作?

(define (subsets s)
   (if (null? s)
       (list nil)
       (let ((rest (subsets (cdr s))))
             (append rest (map <??> rest))
       )
   )
)

猜你喜欢

转载自blog.csdn.net/gggwfn1982/article/details/81448029