Back to index

CSC 378F Data Structures and Algorithm Analysis

DYNAMIC STACKS 
( similar to Dynamic Tables in CLR 18.4 )

We'll implement a stack in an array. As the stack gets too big, the array is doubled in size.

Claim
 ds_1.gif ( push ) ds_2.gif O( 1 )
Idea
Let the elements already inserted pay for the copying :
 
upon inserting
these elements
pay for copying
2
1
1
3
2
1, 2
5
3, 4
1, 2, 3, 4
9
5, 6, 7, 8
1, 2, ... 8
- Each element only pays once.
- Each element pays for copying at most two others.
Invariant
Each element in the top half of the array ( A[ ds_3.gif + 1 ] . . . A[ n ] ) has two credits. Upon copying, the array is full and ds_4.gif elements store 2 credits each, so we use up those 2 * ds_5.gif credits to pay for copying. Upon inserting a new element, we store two credits on it.
ds_6.gif ( push ) = T( push ) + num_bought - num_used

Suppose we copy n elements.

ds_7.gif ( push ) = ( n + 1 ) + 2 - ( 2 * ds_8.gif )
= 3

Suppose we copy 0 elements.

ds_9.gif ( push ) = 1 + 2 - 0
= 3

In a sequence of n pushes, each takes amortized time in O( 1 ).

Alternatively, we could use the Potential Method :

ds_10.gif = ( number of elements in the 'top half' of the array ) * 2
= ( t - ds_11.gif - 1 ) * 2

e.g.

Suppose we copy n elements :

ds_12.gif

 ds_13.gif ( 1 + n ) + ( 1 * 2 ) + ( ds_14.gif * 2 )

 ds_15.gif 3

Suppose we copy 0 elements :
ds_16.gif

 ds_17.gif 1 + [ ( ( t + 1 ) - ds_18.gif - 1 ) * 2 ] - [ ( t - ds_19.gif ) * 2 ]

 ds_20.gif 1 + 2

where the 2 represents ds_21.gif
 ds_22.gif 3
But let's not waste memory. If the stack shrinks dramatically, we should free up some of the unused array space.

Idea
When size shrinks to half the capacity ( i.e. when t drops to ds_23.gif + 1 ), shrink the capacity by half.

Problems
Push n times, where ds_24.gif .
push once
... expands ...
cost O( n )
pop once
... contracts ...
cost O( n )
push once
... expands ...
cost O( n )
pop once
... contracts ...
cost O( n )
etc.
There's no way the amortized cost can be O( 1 ).
Another Idea
When size shrinks to a quarter of the capacity ( i.e. when ds_25.gif ) shrink the array by half. Intuitively, we want many cheap operations before an expensive one.


What pays for the copies?

We can't guarantee credits in A[ ds_26.gif + 1 ] . . . A[ n ], since we might not have inserted anything there. On the other hand, we know that, upon contracting, A[ ds_27.gif + 1 ] ... A[ n ] used to have elements in them, which were popped.

Idea

Upon popping, leave a credit behind in the now-empty slot of A. When we contract, A[ ds_28.gif + 1 ] ... A[ ds_29.gif + 1 ] are guaranteed to contain ds_30.gif + 1 credits, which will pay for copying ds_31.gif elements.

Note that A[ ds_32.gif + 2 ] ... A[ n ] might have credits, but we can throw those away.

So -

ds_33.gif ( push ) = T( push ) + num_bought - num_used

ds_34.gif suppose we don't contact :

ds_35.gif ( pop ) = cost to return one + cost to put on A[ t ] - used
= 1 + 1 - 0 = 2

ds_36.gif suppose we contact from n to ds_37.gif and copy ds_38.gif elements :

ds_39.gif ( pop ) =
T( copy ds_40.gif and return one )
+ T( put on A[ ds_41.gif + 1 ] )
- ( credits in A[ ds_42.gif + 1 ] ... A[ ds_43.gif + 1 ] )

= ( 1 + ds_44.gif ) + 1 - ( ds_45.gif + 1 )

= 1

 ds_46.gif ( pop ) = 1

This doesn't conflict with 'push', so -

ds_47.gif ( pop ) ds_48.gif O( 1 )
ds_49.gif ( push ) ds_50.gif O( 1 )
We can maintain a dynamic stack in amortized constant time per operation!
Potential Method
Note that the potential method is more difficult, since potential depends upon the history ( i.e. the sequence of pushes and pops ).

We could try -

ds_51.gif = max( 2( t - ds_52.gif - 1 ), ds_53.gif + 2 - t )

... where the first expression in 'max' is > 0 for expanding, the second is > 0 for contracting ...

- but the credit approach is more intuitive!
Back to index


Copyright 1998
James Stewart
Dynamic Graphics Project
Department of Computer Science
University of Toronto