3.3.2 表示队列

3.3.2 表示队列
更新子set-car! 和set-cdr!让我们能使用数对组装起数据结构,这些数据结构不能仅用cons,car,cdr来构建。
这部分显示如何使用数对表示一个叫队列的数据结构。 3.3.3部分将显示如何表示叫做表的数据结构。

一个队列是一个序列,它的元素在一个端(队列的结尾)插入,在另一个端删除(在前端)。
图3.18 显示一个初始化为空的队列,加入a和b,删除a,再加入c和d。然后删除b.因为元素删除的顺序
与加入的顺序一致,一个队列有时也称为先进先出的缓冲区。

操作                            结果队列

(define q (make-queue))  
(insert-queue! q 'a)      a
(insert-queue! q 'b)      a b
(delete-queue! q)         b
(insert-queue! q 'c)      b c
(insert-queue! q 'd)      b c d
(delete-queue! q)         c d

图3.18 队列操作

在使用数据抽象方面,我们能认为一个队列被定义成如下的操作的集合:

一个组装子
(make-queue)返回一个空的队列(没有任何的元素)

两个选择子
(empty-queue? <queuq>)测试队列是否为空
(front-queuq <queue>)返回队列头部的元素
如果队列为空,报一个错误,这不修改队列

 两个更新子
(insert-queue! <queue> <item>)在队列的尾部插入一个元素,返回修改后的队列作为值。
(delete-queue! <queue>)在队列的头部删除一个元素,返回修改后的队列作为值。
在删除之前,如果队列为空,报一个错误。

因为一个队列是队列中的项的序列,我们能表示它为有序的列表;队列的头是列表的car部分。
插入一个项到队列中是在列表的末尾加上一个新的元素,删除一个队列中的项是取列表的cdr部分。
然而,这种表示法是低效率的,因为为了插入一个项,我们必须扫描列表到结尾。因为我们有的
仅有的找描一个队列的方法是连续使用cdr操作,对于有n个项的队列,这需要n个步骤数。对列表的
表示的简单的修改,克服了这个缺陷,通过允许队列的操作被实现只要求一个步骤,这样的步骤数量
独立于队列的长度。

这个列表的表示的困难之处是由需要扫描找到列表的结尾处而引起的。我们需要扫描的理由是
尽管表示一个列表的标准方式作为一个数对的链,很容易地提供给我们一个指向列表头部的指针,
但是却没有提供给我们指向列表结尾处的指针。避免这样的问题的修改是表示队列为一个列表,结合
一个附加的指针,指示出列表中的最后一个数对。这样的话,当我们要插入一个项时,我们能使用
尾指针,这避免了扫描这个列表。

一个队列被表示,然后,作为一个指针的数对,front-ptr,rear-ptr.它们相应的指示出,在一个有序的
列表中第一个和最后一个数对。因为我们要队列成一个唯一的对象,我们能使用cons来组合两个指针。因此
队列本身将是两个指针的cons.图3.19演示了这种表示法。

翻译连载: 第59篇 计算机编程的结构与解释   3.3.2 表示队列 - gggwfn1982 - gggwfn1982的个人主页

 
图3.19 一个队列作为一个有头指针和尾指针的列表的实现

为了定义队列的操作,我们使用如下的程序,它让我们能选择和修改一个队列的头指针和尾指针:

(define (front-ptr queue) (car queue))
(define (rear-ptr queue)  (car queue))
(define (set-front-ptr! queue item) (set-car! queue item))
(define (set-rear-ptr! queue item) (set-cdr! queue item))

现在我们能实现了实际的队列操作。我们将考虑一个队列为空如果它的头指针是一个空的列表:

(define (empty-queue? queue) (null? (front-ptr queue)))

make-queue组装子返回一个初始化的空的队列,这是一个数对,它的car 和cdr都是空的列表:

(define (make-queue) (cons '() '()))

为了选择队列的头部的项,我们能返回头指针显示的数对的car.

(define (front-queue queue)
   (if (empty-queuq? queue)
        (error  "FRONT called with an empty queue" queue)
        (car (front-ptr queue))
   )
)

为了在一个队列中插入一个项,我们遵循的方法,它的结果显示在图3.20中。我们首先创建一个新的数对。
它的car是被插入的项,它的cdr是空的列表。如果队列被初始化为空,我们设置队列的头指针和尾指针为
这个新的数对。否则我们修改队列中的最后一个数对,指向这个新的数对,并且设置尾指针指向这个新的数对。

翻译连载: 第59篇 计算机编程的结构与解释   3.3.2 表示队列 - gggwfn1982 - gggwfn1982的个人主页

 
图3.20 在图3.19的队列中,使用(insert! q 'd)的结果

(define (insert-queue! queue item)
  (let ((new-pair (cons item '())))
       (cond ((empty-queue? queue)
                (set-front-ptr! queue new-pair)
                (set-rear-ptr! queue new-pair)
                 queue
             )
             (else
                   (set-cdr! (rear-ptr queue) new-pair)
                   (set-rear-ptr! queue new-pair)
                   queue))
  )
)

为了删除队列的头部的项,我们仅修改头指针让它现在指向队列中的第二个项,
通过找第一个项的cdr指针,我们能找到第二个项。(见图3.21)

翻译连载: 第59篇 计算机编程的结构与解释   3.3.2 表示队列 - gggwfn1982 - gggwfn1982的个人主页

 
图3.21 在图3.20的队列中,使用了(delete-queue! q)的结果

(define (delete-queue! queue)
   (cond ((empty-queue? queue)
            (error "DELETE called with an empty queue" queue)
         )
         (else
            (set-front-ptr! queue (cdr (front-ptr queue)))
            queue)
   )
)

练习3.21
Ben Bitdiddle决定测试如上描述的队列的实现。他写了如下的程序给Lisp解释器,执行如下:

(define q1 (make-queue))
(insert-queue! q1 'a)
((a) a)
(insert-queue! q1 'b)
((a b) b)
(delete-queue! q1)
((b) b)
(delete-queuq! q1)
(() b)

他说“出错了!”“解释器的反应显示了最后的项被插入到了队列两次,并且当我删除项时,第二个b仍然存在,所以队列没有空尽管它认定是空了”。Eva认为Ben已经误解了发生的事。她解释说“项没有插入到队列两次”“仅是标准的Lisp解释器不知道
如何智能地识别出队列的表示方式,如果你要看到队列的打印是对的,你将不得不定义队列的你自己的打印程序”
解释一下Eva说的是什么。特别是显示出为什么Ben的例子生成了它的打印结果。定义一个print-queue程序,
以一个队列为输入,打印出队列中的项的序列来。

练习3.22
代替表示队列为一个指针的数对,我们能构建一个队列作为一个有局部状态的程序。
局部状态包括了一个普通的列表的头部和尾部的指针。
因此,make-queue程序有如下的形式:

(define (make-queue)
   (let ((front-ptr ...)
         (rear-ptr ...))
     <内部程序的定义在此>
     (define (dispatch m) ....)
      dispatch
  )
)

完成make-queue的定义,使用这个表示,提供队列操作的实现。

练习3.23
一个双端队列是一个序列,它的项能被插入和删除在队列的头部或者是尾部。
在双端队列上的操作有组装子make-deque 判断式empty-deque? 选择子front-deque,rear-deque,
和更新子front-insert-deque!,rear-insert-deque!,front-delete-deque!,rear-delete-deque!.
显示使用数对,如何显示双端队列,给出操作的实现程序。所有的操作应该被完成在一个步骤中。

猜你喜欢

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