As an example of this technique, we convert the amortized banker’s queues of Section 3.4.2 to worst-case queues. Queues such as these that support all operations in
O(1)
worst-case time are called real-time queues [HM81].In the original data structure, queues are rotated using ++ and
reverse
. Sincereverse
is monolithic, our first task is finding a way to perform rotations incrementally. This can be done by executing one step of the reverse for every step of the ++. We define a functionrotate
such thatrotate (
f
,r
,a
) =f
++ reverser
++a
ThenThe extra argument
a
is called an accumulating parameter and is used to accumulate the partial results of reversingr
. It is initially empty.Rotations occur when j
R
j=
jF
j+ 1
, so initially jr
j=
jf
j+ 1
. This relationship ispreserved throughout the rotation, so when
f
is empty,r
contains a single element. The base case is thereforerotate ($Nil, $Cons (
y
, $Nil),a
)=
($Nil) ++ reverse ($Cons (y
, $Nil)) ++a
=
$Cons (y
,a
)In the recursive case,
rotate ($Cons (
x
,f
), $Cons (y
,r
),a
)=
($Cons (x
,f
)) ++ reverse ($Cons (y
,r
)) ++a
=
$Cons (x
,f
++ reverse ($Cons (y
,r
)) ++a
)=
$Cons (x
,f
++ reverser
++ $Cons (y
,a
))=
$Cons (x
, rotate (f
,r
, $Cons (y
,a
))) The complete code forrotate
isfun rotate (
f
,r
,a
) = $case (f
,r
) of($Nil, $Cons (
y
, )))Cons (y
,a
) j($Cons (x
,f
0 ), $Cons (y
,r
0 )))Cons (x
, rotate (f
0 ,r
0 , $Cons (y
,a
))) Note that the intrinsic cost of every suspension created byrotate
isO(1)
. Just rewriting the pseudo-constructorqueue
to callrotate
(f
,r
, $Nil
) insteadf
++reverse r
, and making no other changes, already drastically improves the worst-case behavior of the queue operations fromO(n)
toO(log n)
(see [Oka95c]), but we can further improve the worst-case behavior toO(1)
using scheduling.We begin by adding a schedule to the datatype. The original datatype is
datatype
Queue = QueuefF :Stream, LenF : int, R :Stream, LenR : intgWe add a new field
S
of typeStream
that represents a schedule for forcing the nodes ofF
.S
is some suffix ofF
such that all the nodes beforeS
inF
have already been forced and memoized. To force the next suspension inF
, we simply inspect the first node ofS
.Besides adding
S
, we make two further changes to the datatype. First, to emphasize the fact that the nodes ofR
need not be scheduled, we changeR
from a stream to a list. This involves minor changes torotate
. Second, we eliminate the length fields. As we will see shortly, we no longer need the length fields to determine whenR
becomes longer thanF
— instead, we will obtain this information from the schedule. The new datatype is thusdatatype
Queue = Queue offF :stream, R :list, S :streamgstructure RealTimeQueue : QUEUE=
struct
datatypeQueue = Queue offF :stream, R :list, S :streamg (Invariant:jSj=jFj;jRj)
exception EMPTY
val empty = QueuefF = $Nil, R = [ ], S = $Nilg
fun isEmpty (QueuefF =
f
, . . .g) = nullf
fun rotate (
f
,r
,a
) = $case (f
,r
) of($Nil, $Cons (
y
, )))Cons (y
,a
) j($Cons (x
,f
0 ), $Cons (y
,r
0 )))Cons (x
, rotate (f
0 ,r
0 , $Cons (y
,a
)))fun queuefF =
f
, R =r
, S = $Cons (x
,s
)g= QueuefF =f
, R =r
, S =s
g jqueuefF =f
, R =r
, S = $Nilg= let valf
0 = rotate (
f
,r
, $Nil) in QueuefF =f
0 , R = [ ], S =f
0 gendfun snoc (QueuefF =
f
, R =r
, S =s
g,x
) = queuefF =f
, R =x
::r
, S =s
gfun head (QueuefF = $Nil, . . .g) = raise EMPTY jhead (QueuefF = $Cons (
x
,f
), . . .g) =x
fun tail (QueuefF = $Nil, . . .g) = raise EMPTY
jtail (QueuefF = $Cons (
x
,f
), R =r
, S =s
g) = queuefF =f
, R =r
, S =s
gend
Figure 4.1: Real-time queues based on scheduling [Oka95c].
fun snoc (QueuefF =
f
, R =r
, S =s
g,x
) = queuefF =f
, R =x
::r
, S =s
gfun head (QueuefF = $Cons (
x
,f
), . . .g) =x
fun tail (QueuefF = $Cons (
x
,f
), R =r
, S =s
g) = queuefF =f
, R =r
, S =s
gThe pseudo-constructor
queue
maintains the invariant thatjS
j=
jF
j;jR
j(which incidentallyguarantees that j
F
j jR
j since jS
j cannot be negative).snoc
increases jR
j by one andtail
decreasesj
F
jby one, so whenqueue
is called,jS
j=
jF
j;jR
j+ 1
. IfS
is non-empty, thenwe restore the invariant by simply taking the tail of
S
. IfS
is empty, thenR
is one longer thanF
, so we rotate the queue. In either case, inspectingS
to determine whether or not it is empty forces and memoizes the next suspension in the schedule.fun queuefF =
f
, R =r
, S = $Cons (x
,s
)g= QueuefF =f
, R =r
, S =s
g jqueuefF =f
, R =r
, S = $Nilg= let valf
0 = rotate (
f
,r
, $Nil) in QueuefF =f
0 , R = [ ], S =f
0 gendIn the amortized analysis, the unshared cost of every queue operation is
O(1)
. Therefore, every queue operation does onlyO(1)
work outside of forcing suspensions. Hence, to show that all queue operations run inO(1)
worst-case time, we must prove that no suspension takes more thanO(1)
time to execute.Only three forms of suspensions are created by the various queue functions.
$
Nil
is created byempty
andqueue
(in the initial call torotate
). This suspension istrivial and therefore executes in
O(1)
time regardless of whether it has been forced and memoized previously.$
Cons
(y
,a
) is created in the second line ofrotate
and is also trivial. Every call torotate
immediately creates a suspension of the form$case (
f
,r
,a
) of ($Nil, [y
],a
))Cons (y
,a
) j($Cons (x
,f
0 ),y
::r
0 ,a
))Cons (x
, rotate (f
0 ,r
0 , $Cons (y
,a
)))The intrinsic cost of this suspension is
O(1)
. However, it also forces the first node off
, creating the potential for a cascade of forces. But note thatf
is a suffix of the front stream that existed just before the previous rotation. The treatment of the scheduleS
guarantees that every node in that stream was forced and memoized prior to the rotation. Forcing the first node off
simply looks up that memoized value inO(1)
time. The above suspension therefore takes onlyO(1)
time altogether.Since every suspension executes in
O(1)
time, every queue operation takes onlyO(1)
worst- case time.Hint to Practitioners: These queues are not particularly fast when used ephemerally, because of overheads associated with memoizing values that are never looked at again, but are the fastest known real-time implementation when used persistently.